背景
こんばんは。Emacsを触り始めて3~4年たった初心者です。
今回、Emacsのprescient.elパッケージにtiebreak
という機能が追加されたのを機に魔改造(?)してみました。今回はその手順を紹介します。
prescient.elとは
そもそもprescient.elとは、元々ivyやselectrum、companyの補完候補を、履歴
・頻度
・候補の長さ
に並び替えるパッケージで、selectrumのみフィルター機能を提供していました。
このselectrumを皮切りにverticoのようなEmacs標準機能を上手く活用する流れが生まれ、orderless、consult、embark等の現在を代表するパッケージも誕生していきました。
prescient.elもこの流れを汲んで、Emacs標準機能のcompletion-stylesに対応するようになりましたが、フィルター機能を提供するorderlessがとても柔軟性が高く、動作も早いためほとんどの方がorderlessを使用しているように感じます。
筆者もフィルター機能はorderless、並び替えはprescient.elというように使い分けをしていましたが、最近tiebreak
という機能が追加され、長さの並び替えを自由に変更することができるようになりました。
今回は、prescient.elをmigemo対応させ、候補の長さをflxのスコアリングに置き換える方法を紹介します。
migemo
prescient-regexp-regexp
とこちらの記事を参考に下記のように実装します。
(use-package prescient
:config
(setq completion-styles '(prescient)
completion-category-defaults nil
completion-category-overrides nil)
(with-eval-after-load 'migemo
(defun my/prescient-regexp-regexp-advice (orig-fun query &rest args)
"Advice function to replace QUERY with (migemo-get-pattern query)."
(let ((migemo-query (migemo-get-pattern query)))
(apply orig-fun migemo-query args)))
(advice-add 'prescient-regexp-regexp :around #'my/prescient-regexp-regexp-advice))
(prescient-persist-mode +1))
verticoでprescientを有効にします。
(use-package vertico-prescient
:after vertico
:config
(setq vertico-prescient-enable-filtering nil)
(vertico-prescient-mode +1))
ローマ字で日本語が絞り込まれていることが確認できます。
tiebreakerの設定
tiebreakerの設定は下記を参考にスコアリングを行うように修正しました。
https://github.com/radian-software/prescient.el/issues/128#issuecomment-1694358951
fuz-binを利用した設定は下記の通りです。
flxにしました。
lisp初心者なのでもっと良い書き方があれば教えてください…
(use-package flx
:config
(with-eval-after-load 'prescient
;; 入力文字を抽出
(defvar-local my/input-query nil)
(defun my/store-input-query (string &rest _args)
"store the current completion query in `my/input-query'."
(setq my/input-query (replace-regexp-in-string " " "" string)))
(advice-add 'completion-all-completions :before #'my/store-input-query)
;; ローカル変数を使用できるように再定義
(defvar vertico--total nil)
(defvar corfu--total nil)
(defun my/flx-tiebreaker (c1 c2)
(if (and (and (< vertico--total 3000)
(< corfu--total 3000))
(> (length my/input-query) 0)
(< (length c1) 100)
(< (length c2) 100))
(let ((query my/input-query))
(let ((score1 (car (flx-score c1 query flx-file-cache)))
(score2 (car (flx-score c2 query flx-file-cache))))
(if (and (integerp score1) (integerp score2))
(cond ((> score1 score2) -1)
((< score1 score2) 1)
(t (- (length c1) (length c2))))
0)))
(- (length c1) (length c2))))
(setq prescient-tiebreaker #'my/flx-tiebreaker)))
corfu-prescientを適用します。
(use-package corfu-prescient
:after corfu
:hook (corfu-mode . (lambda () (setq-local prescient-filter-method '(fuzzy))))
:config
(setq corfu-prescient-enable-filtering nil)
(corfu-prescient-mode +1))
【応用】orderlessとprescientを組み合わせる場合
prescient.elでも十分ですが、
orderlessはより柔軟に補完候補を絞り込むことができるため、
組み合わせた例も紹介します。
orderlessの設定
orderlessをcompletion-stylesに適用し、
corfuの場合orderless-fast-dispatch・orderless-flexを適用するようにします。
(use-package orderless
:config
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides nil)
(with-eval-after-load 'migemo
;; orderlessをmigemo対応
(defun orderless-migemo (component)
(let ((pattern (downcase (migemo-get-pattern component))))
(condition-case nil
(progn (string-match-p pattern "") pattern)
(invalid-regexp nil))))
(add-to-list 'orderless-matching-styles 'orderless-migemo))
(with-eval-after-load 'corfu
(defun orderless-fast-dispatch (word index total)
(and (= index 0) (= total 1) (length< word 3)
`(orderless-regexp . ,(concat "^" (regexp-quote word)))))
(orderless-define-completion-style orderless-fast
(orderless-style-dispatchers '(orderless-fast-dispatch))
(orderless-matching-styles '(orderless-flex)))
(add-hook 'corfu-mode-hook
(lambda ()
(setq-local corfu-auto-delay 0
corfu-auto-prefix 1
compleion-styles '(orderless-fast basic))))))
flxの設定
(use-package flx
:config
(with-eval-after-load 'prescient
;; 入力文字を抽出
(defvar-local my/input-query nil)
(defun my/store-input-query (string &rest _args)
"store the current completion query in `my/input-query'."
(setq my/input-query (replace-regexp-in-string " " "" string)))
(advice-add 'completion-all-completions :before #'my/store-input-query)
;; ローカル変数を使用できるように再定義
(defvar vertico--total nil)
(defvar corfu--total nil)
(defun my/flx-tiebreaker (c1 c2)
(if (and (and (< vertico--total 3000)
(< corfu--total 3000))
(> (length my/input-query) 0)
(< (length c1) 100)
(< (length c2) 100))
(let ((query my/input-query))
(let ((score1 (car (flx-score c1 query flx-file-cache)))
(score2 (car (flx-score c2 query flx-file-cache))))
(if (and (integerp score1) (integerp score2))
(cond ((> score1 score2) -1)
((< score1 score2) 1)
(t (- (length c1) (length c2))))
0)))
(- (length c1) (length c2))))
(setq prescient-tiebreaker #'my/flx-tiebreaker)))
prescient.elの設定
prescientも設定します。
(use-package prescient
:config
(setq prescient-aggressive-file-save t)
(prescient-persist-mode +1))
(use-package vertico-prescient
:after vertico
:config
(setq vertico-prescient-enable-filtering nil)
(vertico-prescient-mode +1))
(use-package corfu-prescient
:after corfu
:config
(setq corfu-prescient-enable-filtering nil)
(corfu-prescient-mode +1))
終わりに
今回の魔改造(?)はまだまだ完成度としては高くないかもしれませんが、
それでも十分利便性が向上し、とても使いやすくなりました。
cache等を勉強して実装できればいいのですが、まだまだlisp力が足りていないので、
もっと精進していきたいです。