• 作成:

Emacsでhelm-grep-agでジャンプした後xref-pop-marker-stackで履歴マーカーを辿って元に戻る設定

やりたいこと

helm-grep-agなどでプロジェクト内部を移動した後、 smart-jump-backpop-tag-markで元の場所に戻りたい。

現在はスタックに場所を積んでいないため、元の場所に戻れないか、 lsp-modeなどで積まれたかなり昔の位置に戻ってしまう。

方法予測

hookかadviceでhelm-grep-agで移動するときにstackに積む動作を行えば実現可能なはず。

smart-jump前提はやめたほうが良さそう

最初は、 jojojames/smart-jump に乗っかる形で、 smart-jump-stackに積んでいけば良いかなと思いました。

しかし、 elisp-slime-nav-find-elisp-thing-at-pointとかは、 xref-push-marker-stackで積んでいきます。つまりsmart-jump-stackを無視する形になるので、 smart-jumpに依存する機構では使えないメジャーモードが多数発生することになるでしょう。

正確に言うとsmart-jump-backxref-pop-marker-stackを参照するのですが、 smart-jumpが無効になっている環境ではsmart-jump-backを無視してしまうので、使えないということですね。

というかもうsmart-jumpとかggtagsの設定必要ですかね? もう今は大抵の言語でlsp-modeが使えて、使えない言語では最初から諦めてripgrep検索をするから不要になってきた気がします。

JavaとかのLSPはsbtとかだと使えなかったりして大変だからまだ意味がある…? いや、 Support for sbt builds · Issue #172 · emacs-lsp/lsp-java を見る限りEclipseのプロジェクトファイルを作るとかで対応可能みたいですね。

昔のLSP非対応Delphiとかだと意味がありますが…

既存のライブラリを使って解決?

個人的に使っているEmacsパッケージの話. 先日、 Emacs ひな祭りという Emacs 界隈の勉強会が行われました。… | by mopemope | Medium を読んでいて、 jumplistとかいうパッケージにジャンプ関数として登録してしまえば問題解決するのではと思いました。

ganmacs/jumplist: Port of jumplist in vim は7年前で更新が止まっているようですね。もしくはこれはEvilの一部の切り出し版?

変わりに、 gilbertw1/better-jumper: A configurable jump list implementation for Emacs を使う? いやこれ多分全然関係ないですね。

更新が7年前でも動けば問題ない。 jumplistの方を使ってみましょう。だめだこれは独自にmarkerを定義しているから他のジャンプと連携できない。完全に置き換えるなら良いかもしれませんが… xref依存になってるやつを完全に置き換えられる気がしません。

やはりxrefに手動でpushするしかないのでは?

xrefにpushするのは色々大変そうなので避けていましたが、一度やってみましょう、案外簡単かもしれません。

helm-grep-agの実装を辿って実際にジャンプするところを… だーっめんどくせー! 同期的にコールスタック動いてるんだからトップレベルに書いてやれば十分では?

いや雑にやると実際には移動してなくてもスタックにデータが溜まってしまうのでは…

うるせえ! とりあえず実装しろ!

:advice (:before helm-grep-ag (lambda (&rest _ignored) (xref-push-marker-stack)))

とりあえず動きました。

キャンセルした場合にもmarkerにpushしてしまうのはどうにかならないのか

とりあえず動きはしたんですけど、やっぱり懸念していた、「検索したけどやっぱりキャンセルして移動しなかった」場合にもmarkerに位置はpushされてしまいますね。まあ履歴なんてサイズ増やしてやって雑に何度も戻れば良いといえば良いのですが、なんだか気持ち悪いですね。

adviceで渡される引数(返り値でも入ってるんですかね?)を見ても特に判別できる情報は入っていません。

やはり実装内部に立ち入ってキャンセルされてない部分にadviceをかける必要があるのでは?

もうfind-file-hookで実行してしまえば良いのではとも思いましたが、大量に実行される上、デフォルトでxrefをサポートしている関数に対しては2重実行になってしまいますね。

あれなんかとんでもないことが書いてますね。

   (persistent-help :initform "Jump to line (`C-u' Record in mark ring)")

この設定を常にオンにすれば良かったのでは? と思いましたがどうもいにしえのmark ringの話で、 xrefのstackみたいなマーカーとは関係がない? 実際prefixつけても意味がありませんでしたし。

適当にfind-fileで検索したところ、 helm-grep-actionあたりが実際にこれを実行してるっぽい? ならばこれにつけてやれば問題なさそうですね。

:advice (:before helm-grep-action (lambda (&rest _ignored) (xref-push-marker-stack)))

とりあえずこれで満足行く動作は実装できたっぽい?

ついでに履歴のサイズを増やしましょう。

(leaf xref
  :defun xref-set-marker-ring-length
  :config
  ;; 履歴のサイズを上げるために、
  ;; `xref-marker-ring-length'を設定したいだけですが、
  ;; 設定したタイミングでリングのサイズ変更などを行う必要があるので専用関数が用意されているようです。
  (xref-set-marker-ring-length 'xref-marker-ring-length 128))

実際の実装の付近はこの辺です。

.emacs.d/init.el at 2a34ca9e0259a755be97c456a7e1e752f4f73827 · ncaq/.emacs.d

これupstreamに上げるべきなのかなあ

これはhelmのupstreamに上げて、関数内部で実行するべき内容なのか非常に悩みますね。バグフィックスならばそりゃ問答無用で上げますが、これは明らかに機能追加ですし、この機能を望んでいない人もそれなりに居るでしょう。

こういうときにアドバイスでアスペクト指向プログラミングが出来るEmacs Lispは優れていますが、 FirefoxのXULアドオンが滅びた理由と同じ問題を抱えているので悩ましいですね。