9

投稿日

更新日

CompanyからCorfuに移行する

背景

【2023年1月7日】大幅に更新しました。

こんばんは。Emacs歴2年の初心者です。
これまで入力補完にはcompanyを使用していましたが、corfucapeが想像以上に良かったのと、設定に関する日本語記事があまりなかったので書いてみました。
corfuに興味のある方の参考になれば幸いです。

前提条件

corfuはEmacs27以上が必須になります。
また、CUI版Emacsでは使用できません。 corfu-terminalを利用するとCUI版でも使用できます。

セットアップ

今回設定するパッケージは下記のとおりです。

corfu

まずは基本となるcorfuを設定します。

※私はrust-analyzercompany-tabnineのように入力に応じて、補完候補が変化する状況があったので、asyncブランチを採用しています。

init.el
(use-package corfu
  :straight (corfu :type git
                   :host github
                   :repo "minad/corfu"
                   :branch "async"
                   :files (:defaults "extensions/*"))
  :custom ((corfu-auto t)
           (corfu-auto-prefix 1)
           (corfu-auto-delay 0)
           (corfu-cycle t)
           (tab-always-indent 'complete))
  :bind (nil
         :map corfu-map
         ("RET" . nil)
         ("<return>" . nil)
         ("TAB" . corfu-insert)
         ("<tab>" . corfu-insert))
  :init
  (global-corfu-mode +1)

  :config
  ;; java-mode などの一部のモードではタブに `c-indent-line-or-region` が割り当てられているので、
  ;; 補完が出るように `indent-for-tab-command` に置き換える
  (defun my/corfu-remap-tab-command ()
    (global-set-key [remap c-indent-line-or-region] #'indent-for-tab-command))
  (add-hook 'java-mode-hook #'my/corfu-remap-tab-command)

  ;; ミニバッファー上でverticoによる補完が行われない場合、corfuの補完が出るようにします。
  (defun corfu-enable-always-in-minibuffer ()
    "Enable Corfu in the minibuffer if Vertico/Mct are not active."
    (unless (or (bound-and-true-p mct--active)
                (bound-and-true-p vertico--input))
      ;; (setq-local corfu-auto nil) ;; Enable/disable auto completion
      (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                  corfu-popupinfo-delay nil)
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1))

corfu.gif

cape

次にcapeを設定します。
corfuだけでも使用できますが、completion-at-pointを自分好みにカスタマイズしたり、様々なcompletion-at-pointを追加することができるようになります。今回はcompany-yasnippetcompany-tabnine等も組み合わせています。

company-tabnine は非同期モードを搭載したkarta0807913さんのリポジトリを使用しています。

init.el
(use-package company-tabnine
  :straight (company-tabnine :type git :host github :repo "karta0807913/company-tabnine"))

(use-package cape
  :straight t
  :hook ((prog-mode . my/set-basic-capf)
         (text-mode . my/set-basic-capf)
         (meghanada-mode . my/set-meghanada-capf)
         (lsp-completion-mode . my/set-lsp-capf))
  :config
  (defun my/convert-super-capf (arg-capf)
    (list (cape-capf-case-fold
           (cape-super-capf arg-capf
                            (cape-company-to-capf #'company-yasnippet)
                            (cape-company-to-capf #'company-tabnine)))
          #'cape-file
          #'cape-dabbrev))

  (defun my/set-basic-capf ()
    (setq-local completion-at-point-functions (my/convert-super-capf (car completion-at-point-functions))))

  (defun my/set-meghanada-capf ()
    (setq-local completion-at-point-functions (my/convert-super-capf (cape-company-to-capf #'company-meghanada))))

  (defun my/set-lsp-capf ()
    (setq-local completion-at-point-functions (my/convert-super-capf #'lsp-completion-at-point)))

  (add-to-list 'completion-at-point-functions (cape-company-to-capf #'company-tabnine) t)
  (add-to-list 'completion-at-point-functions #'cape-file t)
  (add-to-list 'completion-at-point-functions #'cape-tex t)
  (add-to-list 'completion-at-point-functions #'cape-dabbrev t)
  (add-to-list 'completion-at-point-functions #'cape-keyword t))

cape.gif

  • completion-at-point-functionsは複数のcompletion-at-pointが格納でき、格納された順番に評価されます。
  • cape-capf-case-foldは大文字小文字を区別しないようにしてくれます。
  • cape-super-capfは複数のcompletion-at-pointを一つにまとめることができます。
  • Javaの開発にmeghanadaを使用している場合はcompany-meghanadacape-company-to-capfでcapfに変換します。

※これまでcape-capf-busterを使用して候補の最新化を図っていましたが、corfuasyncブランチがいい感じに動作してくれているので、現在は使用していません。

orderless

次にorderlessを設定します。
Emacs標準の補完スタイルのflexでも構いませんが、orderlessのほうが絞り込み速度が早く、後述のprescientと組み合わせることで使い勝手も向上します。

init.el
(use-package orderless
  :straight t
  :custom ((completion-styles '(orderless))
           (completion-category-defaults nil)
           (completion-category-overrides nil))
  :hook (corfu-mode . (lambda () (setq-local orderless-matching-styles '(orderless-flex)))))

orderless.gif

prescient

presicentは候補を履歴・頻度・長さの順に並び替えてくれるパッケージで、従来はselectrumivycompanyのパッケージしか対応していませんでした。
しかし、最近verticocorfuも対応し、並び替えの恩恵を受けることができるようになりました。
ちなみにprescientcompletion-stylesにそのまま設定することもできますが、orderlessの方がパフォーマンスがいいので、組み合わせています。

【余談】hotfuzzの作者がcompletion-stylesのパフォーマンス測定用のリポジトリを公開してくれているので、興味がある方はお試しください。

init.el
(use-package prescient
  :straight t
  :config
  (prescient-persist-mode +1))

(use-package corfu-prescient
  :straight t
  :after corfu
  :init
  (with-eval-after-load 'orderless
    ;; prescientではなく、補完スタイルを使用して絞り込む
    (setq corfu-prescient-enable-filtering nil
          corfu-prescient-override-sorting t))
  (corfu-prescient-mode +1))

qiita.gif

kind-icon

corfuにアイコンをつけるパッケージです。

init.el
(use-package kind-icon
  :straight t
  :after corfu
  :custom (kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

kind-icon.gif

corfu-doc corfu-popupinfo

補完候補の隣に説明が表示されるようになります。
corfu本体に統合され、extensionsディレクトリに格納されています。
straightを使用している場合はcorfuの宣言箇所に:files (:defaults "extensions/*")を追加します。

init.el
(use-package corfu
  :straight (corfu :type git
                   :host github
                   :repo "minad/corfu"
                   :branch "async"
                   :files (:defaults "extensions/*"))
...(略)...
)

(use-package corfu-popupinfo
  :after corfu
  :config
  (corfu-popupinfo-mode +1))

corfu-doc.gif

yasnippet

yasnippetのキーはcorfuよりも優先順位が高いため、corfu用にTABへキーを割り当てても動作しません。
そのため、yasnippetTABキーを無効化して、別のキーに割り当ててます。

init.el
(use-package yasnippet
  :straight t
  :bind (nil
         :map yas-keymap
         ("<tab>" . nil)
         ("TAB" . nil)
         ("<backtab>" . nil)
         ("S-TAB" . nil)
         ("C-o" . yas-next-field-or-maybe-expand))
  :init
  (yas-global-mode +1))

lsp-mode

lsp-modeはデフォルトでcompanyが起動しますが、corfuが起動するように設定を追加します。

init.el
(use-package lsp-mode
  :straight t
  :custom ((lsp-keymap-prefix "M-l")
           (lsp-signature-auto-activate '(:on-trigger-char :after-completion :on-server-request))
           (lsp-idle-delay 1.0)
           (lsp-semantic-tokens-enable t)
           (lsp-completion-provider :none) ;; ★補完にcorfuを使用する
           )
  :hook ((lsp-mode . lsp-enable-which-key-integration)
         (html-mode . lsp)
         (css-mode . lsp)
         (rust-mode . lsp)
         (nxml-mode . lsp)
         (java-mode . lsp)
         (js-mode . lsp)))

終わりに

corfuは動作が軽量でカスタマイズ性の高い良いパッケージです。
設定を工夫すればcompanyと同等かそれ以上の使い勝手になります。
今後非同期機能がmainブランチに統合される事を願ってます。

参考

Corfu Wiki

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
ログインすると使える機能について
9