この記事は Vim Advent Calendar 2021 11日目の記事です。
My (Neo)Vim RC is here!
背景
なんだかんだVimを使い初めて9年が経っておりました。
これまでも色々なプラグインを追加したり取り除いたり乗り換えたりしてきました。最初は10個ほどだったプラグインも気付けば60前後になっています。
設定ファイルは、ずっとひとつのvimrcを加筆修正してきたわけではなく、たまには全体を見直して調整などをしておりました。 それでも積み重なったものはあるようで、喧嘩しているプラグインや、はたまた組み合わせて拡張したいプラグインなどがでてきました。
そこで、直近で全体的な見直しを行いましたので、その時の思いや変化を書き残したいと思います。
解決したいこと
今回見直すにあたって、解決したかった課題は以下のとおりです。
(1) dein を使いこなせてない
プラグインマネージャーは、最初 NeoBundle
を使っており、 NeoVim 移行のタイミングで dein
を使い始めました。
dein
は、プラグインの読み込みタイミングの細かい制御や、依存関係の管理など、高度な機能を数多く提供しています。しかし、私は実際のところこれを使いこなせていません。
例えば、 g:hoge_auto_enable = 1
が設定してあるときに自動で有効になるプラグインがあるのですが、その設定を書いているにも関わらずファイルの開き方(起動時に開くかVim内から開くか)で動かなかったりしました。
また、プラグインAとBが両方読み込まれたときに有効にしたい設定をどこに記述するのかも悩ましいところでした。私は dein
の設定をTOMLで管理していましたが、片側のhookに書くのは反対側から見えなくなるので違うな……と思っていました。
(2) LanguageServerが二重起動している
6年前はまだLanguageServerは影もありませんでした。また、静的解析系のプラグインもなしで、書いたコードを逐次コンパイル(または実行)して確認していました。 小さいコードのうちはそれで全然問題なかったのですが、コードが大きくなってくるとエラーメッセージの見やすさやコンパイル時間が問題になってきます。
最初に導入したのは Syntastic
でした。これでJavaファイルの保存時にエラーを出すようにしていました。とはいえ、 Syntastic
は同期的にチェックしますので、実行時間が問題になってきます。
そこでしばらく悩んでいたところ、 非同期でエラーチェックしてくれる ALE
を見つけました。これはいろいろなLinter, compilerのエラー情報を非同期でチェックし、まとめて出してくれるプラグインです。 all-in-one 系のプラグインなので、設定もあまりせずに使えていました。
しばらくしてLanguageServerが(私のvimrcに)登場しました。 LanguageServerでは、静的解析や補完の提供、フォーマットなど、IDEの機能を一通り提供してくれます。
私は deoplete
+ LanguageClient-neovim
を使って、こちらで補完のみを行い、静的解析は ALE
で行っていました。
というのも、LanguageServerが提供されたばかりの頃は、あまり安定していなかったり、言語によってまだ「これ」というものがなかったりしており、「linterもLanguageServerも使っていい感じの解析結果になる」くらいの塩梅でした。
この頃から「 ALE
と LanguageClient-neovim
の両方でLanguageServerを起動している」問題がありました。これの何が問題かというと、たとえば rust
ではビルド結果のディレクトリのロックを取り合うので、片方の解析結果が遅れたり、CPUへの負荷が無駄に高くなったりしていました。
それから時が経って、LanguageServerが成熟してきました。linterが全く必要ないとは言いませんが、私の環境ではLanguageServerメインでやっていってもいい頃になってきたなと思っています。
(3) ファイルビューアーが重い
長らく NERDTree
を愛用してきました。これにプラグインを追加して、Gitのステータスを見られるようにしていました。
NERDTree
は他のエディタから入ってきたときに、一番わかりやすく便利なプラグインかもしれません。編集しているファイルの横にファイルツリーを表示したいというのは普通の欲求です。また、 NERDTree
にはデフォルトで(ヘルプ表示を含む)各種キーバインドがあります。覚えるのに少しかかりますが、入れるだけで無設定で使えるのは便利です。
一方で、比較的重いプラグインでもあります。 NERDTree
単体だとそこまでではないのですが、 nerdtree-git-plugin
を入れると少し重くなり(バッファを開くのに若干時間がかかる程度)、 vim-devicons
+ vim-nerdtree-syntax-highlight
の追加で激重になります(j
連打でのカーソル移動が数秒遅延する程度)。
ファイルのアイコン表示自体はいいなと思ったのですが、流石にカーソル移動がままならないのでは使い続けるのは難しいので、より軽量な解決策が必要になりました。
今回の大きな変更点
上記の解決したいことを踏まえて、更新をしていきました。ここでは、大きな変更点を紹介します。
vim-plugへの移行
これは後述するプラグイン設定の管理方法のためにやった部分もありますが主に (1) のために、プラグイン管理を dein
から vim-plug
へと移行しました。
vim-plug
は、長くメンテされているプラグインマネージャで、外部依存がありません。ファイルタイプとコマンド実行時の遅延読み込みはあるものの、他の高度な機能は無く、シンプルなものになっています。
今回その他プラグインマネージャーも見たのですが、これだけ機能があれば十分なことと、長く広く使われているためWebから情報を拾いやすい点でこちらにしました。
プラグイン設定の管理方法
プラグイン設定の管理方法は、ほとんど以下を参考にしました。
ただし、上記はプラグインごとのconfigファイルの場所がruntimepathを前提にしているので、少し変えています(私はvimrcを書き替えるときにvimが使えないと困るので、 nvim -u path/to/init.vim
でなるべくどこでも動くようにしています)。
現在、この方法で問題なく作業できています。特に難しいところはなく、プラグインマネージャーの設定ではなくプラグインや本体の設定改善に時間を使えています。
coc frameworkへの移行
お世話になった ALE
, deoplete
, LanguageClient-nvim
, vim-lsp
を外し、 coc
を導入しました。
踏み切れたのは、LanguageServer関連の成熟が大きいと思います。
また、 coc
は README や Wiki におすすめの設定やmappingが書かれていて、非常に導入しやすかったです。
cocになって、大きく不満がある箇所は特にないです。全体的に問題なく動いています。
今は、直前まで使っていた vim-lsp
との使用感の違い(例えば、CodeActionの対象があったときに、範囲指定が必要かどうかなど)に慣れていっている段階です。
Fernへの移行
NERDTree
から Fern
へ移行しました。関連プラグインと、 Wiki記載の NERDTree
に寄せるマッピング でおよそ違和感なく使えています。
NERDTree と Fern の差を埋める
以下設定で NERDTree
風のコマンドを定義しました。
私は :NERDTreeFind
を多用していたのですが、これに相当するコマンドは Issue に記載されていました。
command! FERNTree Fern . -drawer command! FERNTreeToggle Fern . -drawer -toggle command! FERNTreeFind Fern . -drawer -reveal=% command! FEF FERNTreeFind
コマンド定義はしたものの、絶対に :NEF
(:NERDTreeFind
にマッピングしている) を打ってしまうと思うので、 NERDTree
が読み込まれていないときは NERDTree
系のコマンドを fern
に寄せます。
まず、 UsePlugin
コマンドを拡張して否定形を利用できるようにします。
" UsePlugin 'hoge' で利用チェック " UsePlugin! 'foo' で不使用チェック command! -bang -nargs=1 UsePlugin if <bang>!FindPlugin(<args>) | finish | endif
そして、 fern
を利用していて NERDTree
を入れていないときだけ :NERDTree
を :FERNTree
にマッピングしました。
UsePlugin 'fern.vim' UsePlugin! 'nerdtree' " 絶対打ってしまうと思うのでmapping command! NERDTree FERNTree command! NERDTreeToggle FERNTreeToggle command! NERDTreeFind FERNTreeFind command! NEF FERNTreeFind
プラグインと設定紹介
最後に、いくつかプラグインと設定を紹介します。
easymotion
カーソル移動系のプラグインです。 画面内の任意の位置に、おそらくほぼ最短で辿り着けるプラグインだと思います。
重要な特徴として、 視線を移動目標から動かさずに辿り着ける というのがあるかと思います。
README最初のGIFは、565行目の let s:
の s
へ移動しようとしています。ぜひこれを認識した上でもう一度GIFをご覧いただきたいです。
プラグインの詳細は以下の記事をご覧ください。 haya14busa.com
私の設定ですが、以下のようにして、normalモードでの s
, f
(行内ジャンプ)をよく使います。
nmap s <Plug>(easymotion-s2) xmap s <Plug>(easymotion-s2) map f <Plug>(easymotion-fl) map t <Plug>(easymotion-tl) map F <Plug>(easymotion-Fl) map T <Plug>(easymotion-Tl)
gina
GIt操作系プラグインです。
:Gina status
で git status
と同様のバッファを開き、そこで add
, reset
を行い、 commit
していきます。
それほど多くを覚えずとも使えるのと、複数のファイルをまとめてaddできる点が便利です。
設定としては、 lightline
に情報を表示するのに直接関数を呼ぶのではなく、適当に状態管理して呼び出しを削減し、変数にキャッシュしています。これは、statuslineに重くなり得るコンポーネントを置くとカーソル移動に影響する(遅くなる)事があるからです。
augroup gina-status-lazy autocmd! autocmd CursorMoved,CursorMovedI * call s:f_gina_status('CursorMoved') autocmd CursorHold,CursorHoldI * call s:f_gina_status('CursorHold') autocmd WinEnter * call s:f_gina_status('WinEnter') autocmd WinLeave * call s:f_gina_status('WinLeave') let s:gina_status = 0 let g:gina_status_str = MyGinaRepoStatus() function! s:f_gina_status(event) if a:event ==# 'WinEnter' let g:gina_status_str = MyGinaRepoStatus() let s:gina_status = 2 elseif a:event ==# 'WinLeave' " Do nothing elseif a:event ==# 'CursorMoved' if s:gina_status if 1 < s:gina_status let s:gina_status = 1 else " Do nothing let s:gina_status = 0 endif endif elseif a:event ==# 'CursorHold' let g:gina_status_str = MyGinaRepoStatus() let s:gina_status = 1 endif endfunction augroup END
(今見れば読めたものではないですが、とりあえず呼び出しを削減したかった、ということだけお伝えできればと思います)
vista
LSPなどの情報をもとにファイルの構造情報(アウトラインや関数変数の一覧など)を出せます。
設定することはあまりなく、 :Vista
で構造を表示できます。それだけなのですが、IDEライクになり便利です。
その他個別の設定
現在編集しているファイルの相対パスを取得する
チームで作業しているとき、リポジトリの path/to/Hoge.java
についてコミュニケーションをとりたいときがあるかと思います。
ではそのファイル名をどのように伝えたら良いのでしょうか。
1はシンプルに大変です。
2はファイル名が十分短く一意ならば有効です。一方でファイル名が一意でないときは曖昧な補足情報(「infra
の~」とか)を付け加えたりする必要があります。
3はブラウザを開き、同じファイルにたどり着くだけ手間がかかります。
そういうわけで私は4の方法を取りました。
私は基本的にrepository rootでVimを開いているので、以下のような定義で :CopyFilePath
を定義しました。
function! s:lineNumberWithSeparator(line1, line2) if a:line1 != a:line2 return ": " . a:line1 . "-" . a:line2 endif return "" endfunction """現在のファイルの相対パスを取得 function! RelativeFilePath(line1, line2) return expand("%"). s:lineNumberWithSeparator(a:line1, a:line2) endfunction command -range CopyFilePath let @+ = call('RelativeFilePath', [<line1>, <line2>])
このコマンドは範囲選択にも対応しており、その場合は path/to/Hoge.java: 12-15
のように行番号付きでコピーできます。
また、私の環境では :Cop
で一意になるのでこのコマンドが実行できます。
セッションファイルの保存先設定
vim-session
を使っているのですが、セッションファイルの保存先を以下の優先順位で決めています。
- 現在のディレクトリ直下の .vimsessions フォルダ
- gitリポジトリ配下ならリポジトリroot直下の .vimsessionsフォルダ
~/.config/nvim/default-sessions
また、Gitのブランチごとにセッションを簡単に分けられるよう、 :SaveBranchSession
, :LoadBranchSession
を定義しています。
このあたりの設定は長くなるので 設定ファイル をご覧ください。
おわりに
久しぶりにvimrcの大きな見直しができました。軽量化できたもの、まだ改善したいものなどもありますが、いいデジタル大掃除ができたと思っています。