Lem Advent Calendar 2023 - documentation-mode
これは Lem Advent Calendar の記事です。
概要
Lemでは各コマンドのドキュメントをまとめたページをmarkdownで用意しています。
M-x documentation-describe-bindings
コマンドを使うことでも同じ内容のドキュメントをLem上から表示することが出来ます。
このページは、カーソルの移動や編集、ファイル操作、ウィンドウ操作といったカテゴリ毎にコマンドの一覧がテーブルでまとめられています。
テーブルの各要素はコマンド名、キーバインド、コマンドの説明から構成されており、コマンド名をクリックすると対応するコマンドが定義されているソースコードにジャンプできます。
背景
上記のようなページを手動で保守するのはとても大変です。
そのため、コマンドのドキュメントが更新されたらコマンド一覧ページを生成し、アップロードするまでの手順を自動化する仕組みが必要でした。
生成
ドキュメントを生成するコードはlem/extensions/documentation-mode/internal.lispに置いています。
ページを構成するテーブル用の各クラスインスタンスを作り、マークダウンを出す場合はmarkdown-generator、lemのバッファに出す場合はbuffer-generatorクラスによって出力されます。
デプロイ
GitHubのmainブランチが更新されたときに自動で反映されるようにしています。
そのためGitHub Actionsを用いて、pushされたときにドキュメントに更新があればbotがPRを作るようにしています。
都度マージするのが面倒なので、この辺りはもうすこし改善してもよいかもしれません。
コマンド情報の取得
ページを出力するためには、各コマンドのドキュメントやコマンドの定義位置を取得する必要があります。
コマンドのドキュメント
Lemでコマンドを追加するにはdefine-command
マクロを使います。
define-command
はdefun
等と同じように関数の本体の先頭を文字列にすることで、そのコマンドのドキュメントを書くことが出来ます。
このドキュメントはdocumentation
関数で取り出すことが出来ます。
(documentation #'lem:next-line 'function)
=> "Move the cursor to next line."
source location
コマンドの位置をドキュメントからリンクするためにはsbclのsource locationの情報を使います。
(sb-c:source-location)
関数を呼び出すと、そのソースの位置情報が取得できます。
これをdefine-command
マクロの展開結果に含め、コマンドクラスのインスタンスが保持することで、そのコマンドの定義位置をドキュメント生成時に参照できるようにしています。
例えばnext-lineコマンドの展開結果の一部は次のようになります。
(defclass next-line (primary-command movable-advice)
()
(:default-initargs
:source-location (sb-c:source-location)
:name 'next-line))
モジュール構成
ドキュメントを生成するためには、生成したいドキュメントの構成に合わせてコマンド定義のモジュール構成をリファクタリングする必要がありました。
lem/src/commands/
ディレクトリの各ファイルに標準のコマンドの定義をまとめています。
commands
├── buffer.lisp
├── edit.lisp
├── file.lisp
├── font.lisp
├── frame.lisp
├── help.lisp
├── mark.lisp
├── move.lisp
├── multiple-cursors.lisp
├── other.lisp
├── process.lisp
├── project.lisp
├── s-expression.lisp
├── window.lisp
└── word.lisp
各ファイルは1パッケージ1ファイルで、パッケージ毎にカーソル移動や編集などのカテゴリで分類しています。
おわりに
今回はコマンドのドキュメント生成についてまとめました。
汎用的なものだとCommon Lispなら https://40ants.com/doc/ があるようですが、
ドメインに特化したものなので自分で小さな仕組みを用意したという話でした。
Discussion