9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Emacsのprescient.elを魔改造してみた

Last updated at Posted at 2023-09-12

背景

こんばんは。Emacsを触り始めて3~4年たった初心者です。
今回、Emacsのprescient.elパッケージにtiebreakという機能が追加されたのを機に魔改造(?)してみました。今回はその手順を紹介します。

prescient.elとは

そもそもprescient.elとは、元々ivyselectrumcompanyの補完候補を、履歴頻度候補の長さに並び替えるパッケージで、selectrumのみフィルター機能を提供していました。
このselectrumを皮切りにverticoのようなEmacs標準機能を上手く活用する流れが生まれ、orderlessconsultembark等の現在を代表するパッケージも誕生していきました。

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))

image.png
image.png

ローマ字で日本語が絞り込まれていることが確認できます。

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))

適用前(長さによる並び替え)
image.png

適用後(flxのスコアリングによる並び替え)
image.png

【応用】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力が足りていないので、
もっと精進していきたいです。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?