Lem Advent Calendar 2023 - Electron Frontend
これは Lem Advent Calendar の記事です。
概要
かつてLemにはElectronをフロントエンドにした実装が存在しました。
これによってLemはWeb技術の恩恵を受けられました。
webviewを使用することによってHTMLを表示する事も可能になりました
そしてマルチプラットフォームであることもメリットでした。
Electron frontendが動いていた当時のスクリーンショットです(おそらく2017年のもの)
左のウィンドウでファイルを開き、右のウィンドウでwebviewを使ってドキュメントを表示しています。
背景
2010年代半ば、Electronはモダンな技術でした。
VSCodeやneovimのelectronフロントエンドが出てきたころでもあり、一部のCommon Lispコミュニティの間でもElectronは注目されていました。
その頃、lemのエディタ部分からターミナル入出力用のライブラリであるncursesを使用している部分を切り離し、frontendとbackendを分ける作業をしていました。
また、lemをGUIアプリケーションにするべく、どのGUI ToolKitを使うかを考えている時期でもありました。
そこでElectronを使うことを決めたのです。
代替案
Electron以外を使うことも検討していました。
GTK3
cl-cffi-gtkというCommon Lispのライブラリも存在し、代替案として上がっていました。
断念した理由は、Lem内のウィンドウとGTK3の一つのwidgetを一対一で対応させたかったのですが、アプリケーション実行中に動的にwidgetを作り直す方法がわからなかったためです。
また、electronと比べると少し見劣りする印象があったかもしれません。
QT4
GTK3と同時にこちらも候補としては上がったのですが、
C++を使っている都合上、Common Lisp用のFFIを用意するのが難しく、まともに使えるライブラリは無かった記憶があります。
CAPI
LispWorksというCommon Lisp IDEにCAPIというGUI ToolKitがありました。
これはとても使いやすく、CAPI Frontendを完成させるところまで行きましたが、
LispWorksを750$で購入しないと使えず、OSSとして配布する都合上、無理がありました。
詳細
構成は下記の図のようになっています。
Common Lisp上のGUI Toolkitであれば、一つのプロセスで完結しますが、
Electronを使う場合はプロセスを分ける必要があります。
そのため、JSON-RPCを介してエディタの入出力内容を通信する方法を取りました。
メッセージ内容
JSON-RPC method | 説明 |
---|---|
update-foreground | 前景色を更新 |
update-background | 背景色を更新 |
make-view | ビューを作成したときに呼び出される、electron上では新しいcanvasを作る |
delete-view | ビューを削除したときに呼び出される |
resize-view | ビューのサイズを変更 |
move-view | ビューの位置を移動 |
clear | 一つのビューに対応するcanvasをクリアする |
clear-eol | 行末までクリアする |
clear-eob | バッファ末尾までクリアする |
put | テキストを描画する |
modeline-put | モードラインにテキストを描画する |
touch | 画面を更新する |
move-cursor | カーソルを移動する |
scroll | スクロールする |
js-eval | パラメータで受け取った文字列をevalする https://zed.dev/ |
set-pane | HTMLを文字列として受け取り、新しくそのpaneを追加する |
delete-pane | paneを削除する |
import | JSのreuqireを呼び出す |
set-font | フォントの設定 |
exit | プロセスを終了する |
View
Electron frontendではViewとCanvasが一対一で対応します。
backendからview, x, y, 文字列, 属性(色などの情報)を描画内容として受け取り、Canvasに描画します。
Pane
PaneというHTMLElementを用意しました。
これによってCanvasとは別に、HyperspecやMarkdownをHTMLに変換したものをエディタ上に表示できるようになりました。
Eval
JSPNRPCメソッドとしてjs-evalを用意しました。
これはLemからelectronにJavaScriptの文字列を送信し、Electron側でevalするためです。
Lem backend側でParenscriptで拡張機能を書き、lispからJavaScriptに変換し、electronに渡すことが出来ました。
これによってelectron上の拡張を全てLispで実現することが可能になります。
問題点
パフォーマンス上の問題
canvasに描画する上でのパフォーマンスの問題がありました。
canvasが一つの場合は問題なく動作するのですが、2つ3つと増えたときに再描画がもたつく事があり、これが長年の問題でした。
ちらつきを回避するため、ダブルバッファリングをしていたのも遅くなった理由の一つです。
canvasへの書き込みの時間的局所性があれば、ちらつきが無いようなのですが、その辺りでの最適化を頑張ればよかったかもしれません。
Common LispとJavaScriptの統合の難しさ
LemからJavaScript文字列をElectronに渡すことで、拡張機能をLispで書けるようになったとはいえ、やはりCommon LispとJavaScriptの変換を意識する必要があり、
Common Lisp上で完結するGUI Toolkitに比べると面倒に感じることがありました。
これの解決策として、完全なCommon Lisp to JavaScriptコンパイラがあれば良いのですが、これの可能性としてvaltanがあります。
ただ、Electronランタイムで動く完全な言語処理系を作った上で、パフォーマンス上の問題も解消するとなると道は果てしなく遠いですね…
おわりに
今は亡きElectron frontendについて紹介しました。
夢はありましたし、メンテナンスも再開したいとは思っていますが、はたしてどうなるのでしょうか。
最近はZedなどElectronを使わずネイティブ方面に行っている流れもあるので、今後その手のフレームワークを作る必要が出てくるかもしれません。
Discussion