Emacs
ivy
EmacsDay 22

Emacsの補完&検索を超強化する

私が最初にEmacsを使ったときの感想は「IDEに比べて補完,検索が弱すぎる」でした
この記事ではそんな思いを抱いたEmacserを対象として,Emacsの補完・検索をどうやって強化していくか紹介していきます
helmprojectileは今回の記事では参照しないので,気になる方は「初心者〜初級者のためのEmacs-Helm事始め」などを参照ください)

注) 基本的に私が使っているpackageの紹介になります

コマンド補完

counsel

Emacsで重要になってくるのがM-xで起動する各種コマンドの扱いです
counselivyというコマンド補完機能を用いて,いわゆる絞込検索を実現しています
うろ覚えのコマンドであっても,絞込んでCtrl+n, Ctrl+pで選択できます

out4.gif

ivy自体の設計コンセプトが「より効率的で,より小さく,よりシンプルで,よりスムーズに」なので拡張性が非常に高く,以降紹介するpackageもこのフレームワークが使えます

;; ivy設定
(require 'ivy)
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq enable-recursive-minibuffers t)
(setq ivy-height 30) ;; minibufferのサイズを拡大!(重要)
(setq ivy-extra-directories nil)
(setq ivy-re-builders-alist
      '((t . ivy--regex-plus)))

;; counsel設定
(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "C-x C-f") 'counsel-find-file) ;; find-fileもcounsel任せ!
(defvar counsel-find-file-ignore-regexp (regexp-opt '("./" "../")))

候補補完•検索

swiper

ivyを用いたisearchの拡張です
半角スペースを入れての複数単語による絞込みから,fuzzy検索も可能なので本当に便利
うわさではswiper-helmもあるらしい

swiper.gif

(global-set-key "\C-s" 'swiper)
(defvar swiper-include-line-number-in-search t) ;; line-numberでも検索可能

;; migemo + swiper(日本語をローマ字検索できるようになる)
(require 'avy-migemo)
(avy-migemo-mode 1)
(require 'avy-migemo-e.g.swiper)

似たようなpackageにidoanythingもありますが,そのあたりはぜひ 「君は誰とEmacsる?」を参照してください

company

いわずとしれたEmacsのインテリセンス用package!
auto-completeと双璧を成していますが,auto-completeに比べて気持ち高速で,開発も盛んです
(開発が盛んというのは正義で,3rd party性の拡張packageが出やすく,モダンな言語などに対応しやすいです)

company.gif

(require 'company)
(global-company-mode) ; 全バッファで有効にする
(setq company-transformers '(company-sort-by-backend-importance)) ;; ソート順
(setq company-idle-delay 0) ; デフォルトは0.5
(setq company-minimum-prefix-length 3) ; デフォルトは4
(setq company-selection-wrap-around t) ; 候補の一番下でさらに下に行こうとすると一番上に戻る
(setq completion-ignore-case t)
(setq company-dabbrev-downcase nil)
(global-set-key (kbd "C-M-i") 'company-complete)
(define-key company-active-map (kbd "C-n") 'company-select-next) ;; C-n, C-pで補完候補を次/前の候補を選択
(define-key company-active-map (kbd "C-p") 'company-select-previous)
(define-key company-search-map (kbd "C-n") 'company-select-next)
(define-key company-search-map (kbd "C-p") 'company-select-previous)
(define-key company-active-map (kbd "C-s") 'company-filter-candidates) ;; C-sで絞り込む
(define-key company-active-map (kbd "C-i") 'company-complete-selection) ;; TABで候補を設定
(define-key company-active-map [tab] 'company-complete-selection) ;; TABで候補を設定
(define-key company-active-map (kbd "C-f") 'company-complete-selection) ;; C-fで候補を設定
(define-key emacs-lisp-mode-map (kbd "C-M-i") 'company-complete) ;; 各種メジャーモードでも C-M-iで company-modeの補完を使う

;; yasnippetとの連携
(defvar company-mode/enable-yas t
  "Enable yasnippet for all backends.")
(defun company-mode/backend-with-yas (backend)
  (if (or (not company-mode/enable-yas) (and (listp backend) (member 'company-yasnippet backend)))
      backend
    (append (if (consp backend) backend (list backend))
            '(:with company-yasnippet))))
(setq company-backends (mapcar #'company-mode/backend-with-yas company-backends))

auto-complete

最近まではこちらを使っていましたが速度の問題でcompanyに移行しました
開発者の方(@m2ymさん,@syohexさん)が日本人で,コンタクトをとりやすいというのは大きなメリットだと思います

(require 'auto-complete)
(require 'auto-complete-config)
(require 'fuzzy) ;; fuzzy search (heaby)
(setq ac-use-fuzzy t)
(global-auto-complete-mode t)
(ac-config-default)
(setq ac-delay 0) ;; 補完候補表示までの時間
(setq ac-auto-show-menu 0.05) ;; ヒント表示までの時間
(ac-set-trigger-key "TAB")
(setq ac-use-menu-map t)
(setq ac-menu-height 25) ;; ちょっと大きめにとりましょう!
(setq ac-auto-start 2) ;; 個人的には3でもいいかも
(setq ac-ignore-case t)
(define-key ac-completing-map (kbd "<tab>") 'ac-complete)

;; ac-source(要するにどうやって補完候補を選ぶか)
(setq-default ac-sources 'ac-source-words-in-same-mode-buffers)
(setq-default ac-sources (push 'ac-source-yasnippet ac-sources))

git-complete

git grepを用いた補完エンジンで,Git管理されたプロジェクトであるならば時にcompanyなどの補完よりも優れています.
特に単語レベルではなく行レベルでの補完もできる(なんなら連続して補完も可能)ので,ぶっちゃけ凄いです
詳しくは@zk_phiさんの記事を参照ください

git-complete

(global-unset-key (kbd "C-c C-c")) ;; 一応unbindしておきましょう
(global-set-key (kbd "C-c C-c") 'git-complete)
(add-to-list 'load-path "~/.emacs.d/git-complete") ;; お好きなように
(setq git-complete-enable-autopair t)

eacl

git-completeと同様にGit管理下にあるプロジェクトでの補完用エンジンです
私自身は@zk_phiさんのファンなのでgit-completeを使っていますが,こちらも良いかもしれません

ファイル検索

find-file-in-project

project内のfileをfile名で検索してくれます
当然ivyが効いているので絞込検索が可能です!

(require 'find-file-in-project)
(global-set-key [(super shift i)] 'find-file-in-project)

recentf

絶対に導入しなくてはならない,履歴によるファイル検索packageです
直近に開いたファイルをリスト化してminibufferに表示してくれます
counselと組み合わせて使えば絞込によるファイル移動が可能になります
(cf. 「エコーエリアや Messages バッファにメッセージを表示させたくない」)

;; 余分なメッセージを削除しておきましょう
(defmacro with-suppressed-message (&rest body)
  "Suppress new messages temporarily in the echo area and the `*Messages*' buffer while BODY is evaluated."
  (declare (indent 0))
  (let ((message-log-max nil))
    `(with-temp-message (or (current-message) "") ,@body)))

(require 'recentf)
(setq recentf-save-file "~/.emacs.d/.recentf")
(setq recentf-max-saved-items 200)             ;; recentf に保存するファイルの数
(setq recentf-exclude '(".recentf"))           ;; .recentf自体は含まない
(setq recentf-auto-cleanup 'never)             ;; 保存する内容を整理
(run-with-idle-timer 30 t '(lambda () (with-suppressed-message (recentf-save-list))))
(require 'recentf-ext) ;; ちょっとした拡張

(define-key global-map [(super r)] 'counsel-recentf) ;; counselにおまかせ!

宣言・参照移動

counsel-gitgrep

git-grepcounselによる応用

Screen Shot 2017-12-21 at 14.54.17.png

counsel-rg

git-grepを高速化したrip-grepを用いたgrep検索
counsel-gitgrepよりもこちらのほうがおすすめかも

dumb-jump

これはすごいです!
Emacsではtagを使って関数の宣言まで移動するpackageはいくつかありますが,tagの生成や設定などが面倒でした.
dumb-jumpはそれらを全くすることなく,どの言語でもいい感じに宣言箇所までコマンド1つで移動することが可能になります
(ただし,rubyのような言語では宣言以外の箇所も検索に引っかかってしまうので,そういった時はminibufferでivyを使って絞込検索しましょう)

dumbjump.gif

(setq dumb-jump-mode t)
(setq dumb-jump-selector 'ivy) ;; 候補選択をivyに任せます
(setq dumb-jump-use-visible-window nil)
(define-key global-map [(super d)] 'dumb-jump-go) ;; go-to-definition!
(define-key global-map [(super shift d)] 'dumb-jump-back)

smart-jump

ほんの最近melpaに登録されたpackageで,宣言移動系のpackageを順次実行して宣言まで移動させてくれます
(すいません.まだ使ってません)

その他

neotree

ディレクトリツリーをサイドバーに表示してくれるpackageです
プロジェクトのディレクトリ構成が気になった時などに使いたいですね

neotree

(setq neo-theme 'ascii) ;; icon, classic等もあるよ!
(setq neo-persist-show t) ;; delete-other-window で neotree ウィンドウを消さない
(setq neo-smart-open t) ;; neotree ウィンドウを表示する毎に current file のあるディレクトリを表示する
(setq neo-smart-open t)
(global-set-key "\C-o" 'neotree-toggle)

symbol-overlay

あまり知られていないpackageですが,とても便利です!
カーソル位置の単語を,カーソルが離れてもハイライトしてくれます

symbol-overlay

(require 'symbol-overlay)
(add-hook 'prog-mode-hook #'symbol-overlay-mode)
(add-hook 'markdown-mode-hook #'symbol-overlay-mode)
(global-set-key (kbd "M-i") 'symbol-overlay-put)
(define-key symbol-overlay-map (kbd "p") 'symbol-overlay-jump-prev) ;; 次のシンボルへ
(define-key symbol-overlay-map (kbd "n") 'symbol-overlay-jump-next) ;; 前のシンボルへ
(define-key symbol-overlay-map (kbd "C-g") 'symbol-overlay-remove-all) ;; ハイライトキャンセル

google-this

Emacsから直接ググれます!
(cf. 「selected.el で「選択して右クリック」的な概念を」

(require 'google-this)
(with-eval-after-load "google-this"
    (defun my:google-this ()
      "検索確認をスキップして直接検索実行"
      (interactive)
      (google-this (current-word) t)))

(require 'selected)
(selected-global-mode 1)
(define-key selected-keymap (kbd "g") #'my:google-this)

yasnippet

補完候補用のsnippet群です
とりあえず入れておいて間違いないでしょう!

(require 'yasnippet)
(add-to-list 'load-path "~/.emacs.d/plugins/yasnippet")
(setq yas-snippet-dirs
    '("~/.emacs.d/yasnippets" ;; お好みで!!
    "~/.emacs.d/mysnippets"))
(yas-global-mode 1)

まとめ

最終的に私の補完・検索系の主な設定やキーバインドは以下のようになりました!

(company-mode t) ;; 自動補完
(global-set-key (kbd "M-x") 'counsel) ;; コマンド補完
(global-set-key (kbd "C-s") 'swiper) ;; buffer内検索
(global-set-key (kbd "C-c C-c") 'git-complete) ;; gitによる補完
(global-set-key (kbd "super-shift-i") 'find-file-in-project) ;; ファイル検索
(global-set-key (kbd "C-r") 'counsel-recentf) ;; 履歴によるファイル検索
(global-set-key (kbd "super-f") 'counsel-rg) ;; find-usage
(global-set-key (kbd "C-d") 'dumb-jump) ;; go-to-definition
(global-set-key (kbd "C-o") 'neotree) ;; ディレクトリツリー展開
(add-hook 'prog-mode-hook #'symbol-overlay-mode) ;; symbolへの移動