🔖

Lem Advent Calendar 2023 - execute method

2023/12/22に公開

これは Lem Advent Calendar の記事です。

今回はコマンドの実行方法について焦点を当てていきます。

executeメソッド

コマンドを実行するとき、executeメソッドが呼び出されます。
(defgeneric execute (mode command argument))
引数modeはmodeクラスのインスタンス、commandは実行する対象のコマンドクラスのインスタンス、argumentはC-uで設定した数引数です。

コマンド実行時のフック

コマンドを実行する前後で特定のモードが有効化されている場合のみ、呼び出されるフックを追加したいことが多々あります。

モードがメジャーモードの場合だけだとメソッドを以下のように定義し、

(defmethod execute :before ((mode lisp-mode) command argument)
  ...)

executeメソッドの呼出箇所で、引数modeにbuffer-major-modeを値として渡すことで、上記で定義したメソッドがフックとして作用します。

(execute (buffer-major-mode (current-buffer)) <command> <argument>)

ただ特定のマイナーモードが有効化されている場合ではメジャーモードと同じようにメソッドを定義しても呼び出されません。

(defmethod execute :before ((mode something-minor-mode) command argument)
  ...)

理由としては呼出箇所で(buffer-major-mode (current-buffer))を渡しているからです。
マイナーモードはメジャーモードとは違い、複数有効化されている場合があります。
メジャーモードと全てのマイナーモードのどれかをexecuteメソッドの引数とすると、それ以外は呼び出すことが出来ません。

この解決方法として、lemでは現在のメジャーモード、マイナーモードを全て継承したクラスをexecuteメソッドを呼び出す直前に定義し、そのインスタンスを作っています。
大まかにはexecuteメソッドは次のような呼び出され方をします。

(let ((instance
        (make-instance
         (c2mop:ensure-class <temporary-mode>
                             :direct-superclasses (cons <major-mode>
                                                        <minor-modes>)))))
  (execute instance <command> <argument>))

パフォーマンス

コマンド実行の度にクラス定義とそのインスタンスを作るのは負荷が高すぎるため、エディタのパフォーマンスに影響するようです。
この問題の回避策として、バッファ毎に有効なモードのリストをキーとして、モードのインスタンスをキャッシュするようにしています。

Discussion