Yearly Archives: 2024

2024-06-19

xrefでメソッドへジャンプできなくなる問題に対処する

Emacs Lispをいじっているときに、cl-defmethodで定義したメソッドへM-.(xref-find-definitions)でジャンプできなくなることがたまにあったので次のように対処しました。(Emacs29時点)

;; xrefでメソッドに飛べなくなるのを回避するハック。
;; defgenericを使わずにdefmethodをloadし直すと起きる問題に対処する。
;; `elisp--xref-find-definitions:around'から`find-lisp-object-file-name'を
;; 呼び出したときの挙動を変更する。
(defun my-elisp--xref-find-definitions:around (old-fun &rest args)
  (cl-letf* ((flofn-old (symbol-function 'find-lisp-object-file-name))
             ((symbol-function 'find-lisp-object-file-name)
              (lambda (object &rest flofn-args)
                (or (apply flofn-old object flofn-args)
                    ;; 本来のfind-lisp-object-file-nameの結果がnullでかつ
                    ;; OBJECTがgeneric関数シンボルなら
                    ;; 空のファイル名を返すことで
                    ;; elisp--xref-find-definitions内の
                    ;; メソッド列挙部に到達させるハック。
                    ;; 本来ならその部分のFIXMEにも書いてある通り
                    ;; その部分を`elisp-xref-find-def-functions'に分離
                    ;; すべきだと思われる。
                    (when (and (symbolp object) (cl--generic object))
                      "")))))
    (apply old-fun args)))
(advice-add 'elisp--xref-find-definitions :around
            'my-elisp--xref-find-definitions:around)

根本的な原因は私がcl-defgenericせずにcl-defmethodしているからなのですが、まぁ、そこは良いんです。かったるくてやってられないから省いているだけので。それがダメというならmy-defmethodでも作らにゃなりません。しかし、まぁ、そのツケが回ってきたというだけの話ではあります。

xref等はsymbol-file関数を使ってシンボルを定義したファイルを特定しますが、そのsymbol-fileload-history変数に記録されたファイルとシンボル定義の対応表(alist)を参照します。elファイルをloadするとその過程で定義されたシンボルがこの変数に記録されるわけですが、cl-defmethodは既にgeneric関数が定義されているときには新たにgeneric関数を定義しないので、同じelファイルを2回ロードすると暗黙的に作成されたgeneric関数のファイル名が消えてしまいます。elisp--xref-find-definitionsはgeneric関数のファイル名が取得できないとメソッドのファイル名も列挙しないので、結果定義されているはずのメソッドに飛べなくなるわけです。

上の変更では、generic関数のファイル名が特定できなかった場合に空文字列のファイル名を返すことで特定できたことにして、メソッドの列挙部分に無理矢理処理を通します。幸いなことにxrefは空文字列のファイル名を無視してくれる(ジャンプ先候補に出さない)ようです。

ろくなもんじゃありませんけど、とりあえずはこれで。

ちゃんとやるならelisp--xref-find-definitionsのFIXMEコメントに書かれているようにgeneric&method列挙部分をelisp-xref-find-def-functions変数に登録する関数としてくくりだした上で、その中でgeneric関数のファイル名が特定できなくてもメソッドのファイル名を列挙するようにするのが良さそうです。

今回はシンボルと定義ファイル名の割り出し方法に関するお勉強でした。

2024-06-08 ,

org-inline-image-fixのorg-mode 9.7対応

org-modeでインライン画像化する画像形式を限定するの続き。

org-mode 9.7がリリースされたので関係する変更点を調べてorg-inline-image-fixに必要な修正を加えました。

9.7のインライン画像周辺の変更点としては次のものが見つかりました:

いくつかは前回対応しましたし、取りこむ必要が無いものもあります。

  • org-image-max-widthの追加
  • org-image-alignの追加

の二つは一応取りこんでおくことにしました。

org-image-max-width は org-limit-image-size と機能が被りますが、それぞれ独立して機能するのでどちらを使っても問題ありません。私は高さの制限が出来る後者を使い続けます。まれに縦長の画像に出くわすことがあるので。

org-image-align の方は、私は中央寄せや右寄せの指定を普段しないので多分使わないと思います。一応試したらこんな感じになりました:

Org9.7のインライン画像alignプレビュー機能を使ってみたところ
図1: Org9.7のインライン画像alignプレビュー機能を使ってみたところ

一箇所、インライン画像の右側にある空白を画像のオーバーレイに含めてしまう(空白を消して表示する)修正に疑問があったので、本家とは違う修正をしました。おそらく中央寄せや右寄せにするときに右側に空白があると完全な位置に寄らないのでそれを解消する意図があるのだと思いますが、個人的には空白が(存在するのに)勝手に消えて表示されるのはあまり好ましくないような気がします。とりあえず左寄せの時は従来通りに必ず空白を残すようにしておきました。

2024-06-08 ,

インポートとジェネレータ

el-easydraw(以下edraw)の最近の変更の中で比較的大きかったのはインポート機能でしょうか。

これは元々edrawが一部のSVGを正しく読み込むことが出来ないという問題を指摘されたことから開発に至った機能です。

元々edrawは膨大なSVG仕様の全てに対応する気はさらさら無く、edrawによって作成・出力したSVGのみが再編集可能です。edrawが出力したSVGには <g id="edraw-body">...</g> という形のグループ要素がありその中が唯一編集可能な領域になっています。idがedraw-bodyなグループ要素(つまりg#edraw-body)の中にはedrawが対応しているものしか入っていない前提で作られているため、無理矢理そこに何かを入れたとしても動作は保証されません(いや、まぁ、どのみち誰も何も保証しませんが)。とは言えその方は他のツールで作成したSVGを持ってきたかったらしく、そのための仕組みとして最終的にインポート機能を作ることになったわけです。

特にedraw-modeを使うと任意のSVGファイル(を開いたバッファ)で作図エディタを起動することが可能だったのですが、そんなことをしても当然そのSVGの中にあった図形を編集できるわけも無く、せいぜいその上に新しい図形を乗せていくことくらいしか出来ませんでした。

現在では、他で作った(g#edraw-bodyが存在しない)SVGでM-x edraw-modeを実行するとエラーが出て、諦めるか自己責任で M-x edraw-convert-buffer-to-edraw-svg-xml を実行して変換するよう勧めるメッセージが表示されます。実際に edraw-convert-buffer-to-edraw-svg-xml を使用すると、バッファ内のSVGがedrawが扱えそうな形に変換されます。対応していない要素は除去されたり警告が出たりします。そして再度 M-x edraw-mode を実行すると、運が良ければ編集できることでしょう。

作図エディタの中からでもメインメニューの「ドキュメント」→「ファイルからインポート...」を選択して任意のSVGファイルを選べばそれを変換・取りこむことができます。

試しにInkscapeで作成したSVGを読み込んでみましたが、まぁ、思ったよりは取りこめる感じでした。他にもGraphvizで作成したSVGを取りこんだりも出来ました(ただしグラフ構造を手軽に再編集できるようなものではありません)。その方はdvisvgmの出力を取りこみたかったようですが、それも最終的にはうまく取りこめるようになりました。

dvisvgmを使用すればlatexで生成したものをSVGに変換できます。つまり、例えば数式をSVGに変換することが可能です。そしてそれを取りこめるようになったわけです。

しかしこのままでは数式を図の中に取りこむには手間がかかりすぎます。まずlatexのソースを書いて、latexでdviを作成し、それをdvisvgmでsvgへ変換し、edrawでインポートしなければなりません。もっと効率よく出来る仕組みが必要です。

私も数式を図に取りこめたら良いだろうなと思ったことはあったので、そのための仕組みを作ることにしました。

「数式ツール」のようなものを作っても良かったのですが、それでは直接的すぎて面白くありません。そこで考えたのが「ジェネレータ」です。

ジェネレータは何らかの設定から図形を生成するような(図形として配置可能な)オブジェクトです。ジェネレータツール(生成ツール)で配置できます。

ジェネレータにはプロパティとして生成の元(ソース)となるような情報を指定出来ます。まず第一に生成タイプがあり、今のところlatexかgridが指定出来ます。次に生成ソースを指定するプロパティがあり、タイプがlatexの時はそこにLaTeXのソースコードを記述できます。最後に生成オプションがあり、これはタイプによって異なる追加のプロパティを色々設定できます。

そして生成ボタンを押すとそれらのソース情報を元に図形が生成されるという寸法です。latexの場合は、preamble部分(カスタマイズ変数で変更可能)などと合成されてlatex、dvisvgmコマンドが呼び出され、生成されたSVGがedraw用に変換されてジェネレータの子要素として挿入されます(もちろんlatexとdvisvgmが必要です。私はTeX Liveでインストールしましたが、MSYS2にPATHが通っていると色々ハマるのでご注意を)。

latexジェネレータで数式を生成したところ
図1: latexジェネレータで数式を生成したところ

生成タイプのgridは格子状の線を生成するものです。生成タイプがlatexだけでは格好が付かないので適当に作ってみました。線が太くて気に入らないという方は位置を0.5ピクセルずらしてみて下さい。「これもうソースコード要らないじゃん!」ということが発覚してちょっと後悔しています。

数式をSVGに入れるならMathMLを使うという手もあります。実際latexからMathMLに変換してforeignObject要素として取りこむような生成タイプを作ることもできるでしょう。しかし一番のネックはlibrsvgが対応していないのでEmacsの中で表示されないことでしょうね。

latexの中で(tikzで)グラフを生成することもできるみたいです。

latexの図を生成したところ
図2: latexの図を生成したところ

これらの機能で簡単に複雑な図を表現できるようになったのは良いところですが、反面データサイズが膨らみがちなことが困りそうなところでしょうか。特に [[edraw:data= 形式のリンクはデータがOrgファイル内(特に一行に)埋め込まれるので、あまり大きいと何か問題を引き起こすかもしれません。単純にインライン画像をoffにしたときに見栄えが悪いというのもあります(今更ですが)。そういう場合はインライン画像を右クリックして [[edraw:file= 形式へ変換して下さい。