🔖

Lem Advent Calendar 2023 - Electron Frontend

2023/12/06に公開

これは 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