Emacsの補完機能をHelmからIvyへ移行

バズってたQiitaの記事を参考に、HelmからIvyに移行してみた。

qiita.com

自分が加えた変更は次のとおり。

counsel-M-xの絞り込み開始後のソーティング方法を辞書順に

長さでソートした後、さらに辞書順でソートするようにした。

(defun ivy--sort-by-len (name candidates)
  "Sort CANDIDATES based on similarity of their length with NAME."
  (let ((name-len (length name))
        (candidates-count (length candidates)))
    (if (< 500 candidates-count)
        candidates
      (seq-sort-by (lambda (candidate-string)
                     (cons candidate-string
                           (abs (- name-len (length candidate-string)))))
                   (lambda (a b)
                     (if (not (= (cdr a) (cdr b)))
                         (< (cdr a) (cdr b))
                       (string< (car a) (car b))))
                   candidates))))
(dolist (i '(counsel-M-x
             counsel-apropos
             counsel-describe-function
             counsel-describe-variable
             counsel-describe-face))
  (setf (alist-get i ivy-sort-matches-functions-alist) 'ivy--sort-by-len))

helm-ls-gitパッケージの代替コマンドを定義

helm-ls-gitに相当するcounselのパッケージが見つからなかったので、 Find file in a Git repo with ivy · (or emacsを参考に代替コマンドを定義した。

追記: あとで見直したらcounsel-gitというコマンドが定義済みだった。なんで見逃したんだろ。

"other window"アクションをどうしても追加したくて方法がわからず苦労したが、ソースコードやヘルプを色々と探したらどうやらivy-set-actionsで設定することが分かった。 ivy-readcallerは何のために設定するのかと思ったらivy-set-actionsに登録するコマンド名と対応するようだ。

;; replaced from helm-ls-git
(ivy-set-actions
 'counsel-git-ls
 '(("j" find-file-other-window "other window")
   ("f" find-file-other-frame "other frame")))
(defun counsel-git-ls (&optional initial-input)
  "Find file in the current Git repository.
When INITIAL-INPUT is non-nil, use it in the minibuffer during completion."
  (interactive)
  (let* ((default-directory (locate-dominating-file
                             default-directory ".git"))
         (candidate-git-files (split-string
                               (shell-command-to-string
                                "git ls-files --full-name --")
                               "\n")))
    (ivy-read "Git files: " candidate-git-files
              :initial-input initial-input
              :action #'find-file
              :caller 'counsel-git-ls)))