• 作成:
  • 更新:

Emacsのlsp-haskell(lsp-mode)で1コマンドでGHCの提供する型定義を挿入したい

haskell-language-serverによってHaskellの開発環境に調和がもたらされましたが、型挿入を選択するのが面倒

haskell/haskell-language-server: Successor of ghcide & haskell-ide-engine. One IDE to rule them all. はHaskellのエディタ環境にやっと調和を齎してくれました。

Happy Haskell Programming で行っていた、 GHCの推論する型注釈をトップレベルに挿入する機能も持っています。

しかしこれがちょっと面倒で、 lsp-execute-code-actionを実行して一番下にあるadd signatureを選択しなければいけません。

私は適当にコードを書いて、最後にGHCの推論している正しそうな型を一気に関数たちに適用していくと言うスタイルでコードを書く時もあります。その場合いちいちadd signatureを選択するのは面倒ですよね。

逆に型だけ考えて内容をundefinedで埋めておいたりすることもありますが、そのへんは臨機応変に対象とするコードのスタイルに合わせています。

なので、選択しなくて良いようにlsp-mode向けの単独コマンドを作成しました。

あんまりlsp-mode弄らないし、 Emacs Lispのハッシュテーブルとかもおまり使わないので少し手子摺りました。

開発

元の動作が、 lsp-execute-code-action を経由するものなのでこれを改造していく方針で良さそうです。

lsp-execute-code-actionlsp-code-actions-at-pointでアクションを取得して、それをlsp--execute-code-actionに渡しているようですね。

lsp-code-actions-at-pointで取得できるアクションはハッシュテーブルのリストになっているので、 seq-findadd signatureを持つアクションを選択して渡せば良いでしょう。

ただ、 lsp--execute-code-actionはハイフン2つなのでprivateメイタ関数なので、 lsp-execute-code-actionに引数を指定して渡してやるのがより良いでしょう。

出来ました

lsp-modeの複雑さから最初は難航するかと思いましたが、案外すんなりと関数が出来ました。

(defun lsp-haskell-execute-code-action-add-signature ()
  "Execute code action of add signature."
  (interactive)
  (let ((action (seq-find (lambda (e) (string-prefix-p "add signature" (gethash "title" e))) (lsp-code-actions-at-point))))
    (if (hash-table-p action)
        (lsp-execute-code-action action)
      (message "I Can't find add signature action for this point"))))

とりあえず自分で使う分にはこれで良いとして、一応、 emacs-lsp/lsp-haskell: lsp-mode haskell にもこの関数を還元してみますか、受け入れられるかは分かりませんが…

ファイル全体にかけるのは…まあとりあえずは良いや

この記事を書き終わってから、ポイントの位置に関わらず警告されている場所にシグネチャ全部載せるコマンドも欲しかったことを思い出しました。とりあえずそれはまた今度で良いか…

lsp-haskellに寄贈する上での問題点

seq-findはEmacs 25からの標準添付関数なんですね。 lsp-haskellはEmacs 24を下限にしていて、 Emacs 24はギリギリサポートする価値のあるバージョンですね。

と思ったら価値はありません。何故かと言うとlsp-modeのサポートEmacsバージョンが26.1だからです。 lsp-haskellで単独利用することはないので、 lsp-haskellの依存バージョンも上げて良いでしょう。

と言うわけでPR作りました。

取り込まれるかは知らない。

added: lsp-haskell-execute-code-action-add-signature by ncaq · Pull Request #129 · emacs-lsp/lsp-haskell

lsp-haskellの有識者にこれだとダメだと指摘されました

lsp-use-plistsと言うカスタム変数があるらしく、これを使うとデータ構造ハッシュマップではなく連想リストになるらしい。

なのでgethashのようなハッシュマップを直接取得する関数は使っちゃダメです。

何故変更出来るようにしているのか、出来るようにしてるのに何故ハッシュマップを直接見せるインターフェイスにしているのか、謎が多すぎる。

リファレンスを示してくれたので変更は容易でした。

(defun lsp-haskell-execute-code-action-add-signature ()
  "Execute code action of add signature.
Add the type signature that GHC infers to the function located below the point."
  (interactive)
  (let ((action (seq-find
                 (lambda (e) (string-prefix-p "add signature" (lsp:code-action-title e)))
                 (lsp-code-actions-at-point))))
    (if action
        (lsp-execute-code-action action)
      (message "I can't find add signature action for this point"))))