20行ぐらいでリッチなテキストエディタをJavaFXで

Nashorn スクリプトで作る

ace.js として以下のスクリプトを作成します。

#!/usr/bin/jjs -fx 

var html = "<!DOCTYPE html>"
  + "<html><head>"
  + "<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.2.9/ace.js'></script>"
  + "<script>"
  + "  var editor = ace.edit('editor');"
  + "  editor.setTheme('ace/theme/monokai');"
  + "  editor.getSession().setMode('ace/mode/javascript');"
  + "</script>"
  + "</body></html>";

var webView = new javafx.scene.web.WebView();
webView.engine.loadContent(html);
var pane = new javafx.scene.layout.BorderPane(webView);
$STAGE.scene = new javafx.scene.Scene(pane, 500, 400);
$STAGE.show();

実行権限付けて実行すれば

$ ./ace.js

こんな感じのテキストエディタになります。

f:id:Naotsugu:20180110233410p:plain

シンタックスハイライトなども。

f:id:Naotsugu:20180110233916p:plain

WebView で Ace Editor を使ってるだけです。

Ace Editor とは

Cloud9 IDE で使われているエディタコンポーネントです。

blog1.mammb.com

この間 AWS でも、AWS Cloud9 – クラウド開発環境 としてサービスが開始されたやつですね。

Ace Editor は JS なのでブラウザで簡単に実行できますし、ローカルのアプリケーションに組み込みたい場合でも JavaFX で WebView の中で動かしたりできます。

50行ぐらいでファイル処理

もう少しだけ加工してみます。

最初に html ファイルを外に出します。ace.html として以下を作成しましょう。

<!DOCTYPE html>
<html>
<head>
<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.2.9/ace.js"></script>
<script>
    var editor = ace.edit("editor");
    editor.setTheme("ace/theme/monokai");
    editor.getSession().setMode("ace/mode/javascript");
</script>
</body>
</html>

外出した html ファイルは以下のように WebView で読み込むことができます。

webView.engine.javaScriptEnabled = true;
webView.engine.load(new java.io.File("ace.html").toURI().toURL().toString());

後に executeScript() するので javaScriptEnabled = true としておきます。


メニューを作ります。Open と Save だけの簡単なものにします。

var menuBar  = new javafx.scene.control.MenuBar(),
    menuFile = new javafx.scene.control.Menu("File"),
    itemOpen = new javafx.scene.control.MenuItem("Open"),
    itemSave = new javafx.scene.control.MenuItem("Save");

menuFile.items.addAll(itemOpen, itemSave);
menuBar.menus.addAll(menuFile);
if (java.lang.System.getProperty("os.name").startsWith("Mac")) {
    menuBar.useSystemMenuBarProperty().set(true);
}

// ...

var pane = new javafx.scene.layout.BorderPane(webView, menuBar, null, null, null);

Mac の場合はシステムメニューバーのプロパティを設定します。


最後にメニュー選択時のアクションを追加します。

Open 選択時のアクションです。

itemOpen.setOnAction(function(e) {
    var selected = new javafx.stage.FileChooser().showOpenDialog($STAGE);
    if (selected) {
        file = selected;        
        var text = new java.lang.String(Files.readAllBytes(file.toPath()), "UTF-8");
        var editor = webView.engine.executeScript("editor");
        editor.call("setValue", text);
    }
});

webView.engine.executeScript("editor") とすると WebView 内の Ace editor のJSオブジェクトが JSObject として得られるので、call() により Ace editor のテキスト内容を設定します。

Sava 選択時のアクションです。

itemSave.setOnAction(function(e) {
    file = file || new javafx.stage.FileChooser().showSaveDialog($STAGE);
    if (file) {
        var text = webView.engine.executeScript("editor.getValue()");
        Files.write(file.toPath(), text.getBytes("UTF-8"));
    }
});


最終的には以下のようになりました。

#!/usr/bin/jjs -fx 

var Files = Java.type("java.nio.file.Files");

var webView = new javafx.scene.web.WebView(), file;
var menuBar  = new javafx.scene.control.MenuBar(),
    menuFile = new javafx.scene.control.Menu("File"),
    itemOpen = new javafx.scene.control.MenuItem("Open"),
    itemSave = new javafx.scene.control.MenuItem("Save");

webView.engine.javaScriptEnabled = true;
webView.engine.load(new java.io.File("ace.html").toURI().toURL().toString());

itemOpen.setOnAction(function(e) {
    var selected = new javafx.stage.FileChooser().showOpenDialog($STAGE);
    if (selected) {
        file = selected;
        var text = new java.lang.String(Files.readAllBytes(file.toPath()), "UTF-8");
        var editor = webView.engine.executeScript("editor");
        editor.call("setValue", text);
    }
});

itemSave.setOnAction(function(e) {
    file = file || new javafx.stage.FileChooser().showSaveDialog($STAGE);
    if (file) {
        var text = webView.engine.executeScript("editor.getValue()");
        Files.write(file.toPath(), text.getBytes("UTF-8"));
    }
});

menuFile.items.addAll(itemOpen, itemSave);
menuBar.menus.addAll(menuFile);
if (java.lang.System.getProperty("os.name").startsWith("Mac")) {
    menuBar.useSystemMenuBarProperty().set(true);
}

$STAGE.scene = new javafx.scene.Scene(
    new javafx.scene.layout.BorderPane(webView, menuBar, null, null, null), 
    500, 400);
$STAGE.show();

実行

実行してみます。

$ ./ace.js

ace.js を開くと以下のようになりました。

f:id:Naotsugu:20180111204222p:plain

簡単ですね!

Ace editor は110を超える言語のシンタックスハイライトがあり、テーマも20を超える数が用意されています。 アプリケーションでエディタコンポーネントが必要な場合の良い選択肢になると思います。

Ace - The High Performance Code Editor for the Web