CREATIVE X 第2弾
CREATIVE X 第2弾
2018.07.20

Vimをもっと上手に! 新たな旋風、Neovimで自堕落なVim力に喝を入れる。

まさくに

2018年、都内某所の上野。

社内にせまるPhpStorm、Atom、VSCodeの侵攻におののいた僕は、みずからの貧弱なVim力を嘆き、その力をアップデートしなければならぬ、とただただ神に祈っていました。

思えば僕は、Vimをまったく使いこなしてはいなかったのです。先日、Vim8.1がリリースされました。アップデートでもしてみようかな、よっこらしょいとキーを叩き、vimrcを開いたとき、ふいに気づいたのです。

そこに広がるのは愛のないコピペの嵐、辻褄を合わせるためだけの追記の設定、書いてあるものの覚えてもいないキーバインド――。黒いターミナルを見ながら、気づくとツーッと一条の涙が僕の頬を流れていたのでありました。このままでは……いけない。

昨今のIDEはマヂ凄いです。それはわかる。PhpStormの静的解析とかエグい。このタイミングでIDEに居を構えるのもいい。何も悪いことじゃないさ。でも僕は――。Vimを使おうと決めた在りし日の自分を思い出しました。僕はVimmerになりたい。Vimの持つポテンシャルをまったく発揮していなかったにも関わらず、みずからの不甲斐なさを棚に上げてVimを見限っていいのだろうか。「僕自身」というボトルネックを解消しなければ、何を使っても一緒なのではないだろうか。

そう、今ふたたびのVimへ!
 
 
 
rm -f ~/.vimrc
ドーン!
 
 
 
なればこそ、なればこそ、思い切ってvimrcを一度捨てました。もう一度、再構築して有意義なvimrcを作るために。これが僕の覚悟です。本稿では多くの説明を抜きにして、今僕が使ってるNeovimの設定を話します(結局Vim8.1ではない)。

Vim8.1かNeovimにするか

Vimは5月にバージョン8.1が出て、Terminal機能をサポートしました。僕はiTerm2でVimも立ち上げていて、正直「iTerm2のタブを使えば良くない? 画面も小さくならないし」と思っていたので、8.1にはあまり惹かれるものはありませんでした。

ただ僕には「NeoBundle、いつまで使ってんだろ……」という危機感があったため、Vim8.1の導入を機にその後継・dein.vimも合わせて入れようと思っていました。このとき、Neovimのことは考えていませんでした。

Neovimは噂だけは聞いたことがありました。「Vimを愛してやまない人たちが作り上げた新たなるVim」、「コミュニティ主導で動く開発速度の早いVim」、「今、向こうの砂漠のオアシスへ来ているらしい新たな王国のカリスマ」という、まことしやかな噂です。それでも別に僕が導入する理由になりません。僕は「本家」という言葉が好きですし、不足を感じていたのは僕の能力であってVimの責ではありません。

予定通りVim8.1を入れました。それからdein.vimも入れました。ただdein.vimを入れ替えたならdenite入れたいし、そうなるとdeopleteを入れたくなってくるというのが人情というものです。そうですよね? いつもありがとうございます。

そうなるとどうなるかっていうと、Vim8 + deopleteですとdeoplete単体では動けなくなってしまうのです。Vimとやり取りをするためにroxma/nvim-yarproxma/vim-hug-neovim-rpcの2つも合わせて必要になります。結論、これでVim8.1でも動きました。しかしNeovim + deopleteですとこの2つは不要のようでした。いやぁ。

い や ぁ 。

こうなってくるともう誰でも試すと思うんですよ。Neovim。しょうがなくないですか。何か新しいウェイヴを感じますもん。Neovimはまだバージョン0.3ですが、Vimの互換をほとんど保っているといいますし、速度も早いらしい。それでもいろいろ考えたのですが、「VimもViから始まったのでは?」という事実に思いあたり、ちょっとだけ、ちょっとだけNeovimを使うことにしました。

今は「ちょっとだけNeovimを使う」状態なので、Vim8.1とvimrcはそのままにNeovimの設定 ~/.config/nvim/init.vim にはそのシンボリックリンクをあてています。なので、ここから先はNeovimの流儀にのっとって、vimrcではなくinit.vimと言うようにします。あ、dein.vimを使うならたぶん設定ファイルは分けたほうがいいと思うし、普通分けるのだと思います。僕はまだ分けてないけど。

それではここから、実際にNeovimを導入していきます。

Neovimのインストール

macOSであればNeovimは下記のコマンドで入ります。

$ brew install neovim # 本体のインストール

今やってみたら最新の0.3が入りました。簡単ですね。で、これで入ったと思って起動するとエラーが表示されるかもしれません。この時点では、まだ僕のinit.vimはすっからかんなのでエラーは起きないんですが、話を進めると後述のdeopleteがpython3をご所望で……。プラグインを入れると下記のエラーが起きるようになります。

[deoplete] deoplete failed to load. Try the :UpdateRemotePlugins command and restart Neovim. See also :checkhealth.

今のうちに望みのままに与えるためにPython3の準備をします。僕はpythonはpyenvで入れています。pipで入れるのはneovimという名前ですが、Neovim本体ではありません。Neovimのクライアントです。このクライアントを通してNeovimとpython3がやり取りを行うようですね。ちょっと僕も戸惑いました。

$ pip install neovim
Collecting neovim
  Using cached https://files.pythonhosted.org/packages/e4/57/48ad9ec3687c40aed58146e2ac539e0e0134ec452db905560b77fedf7b69/neovim-0.2.6.tar.gz
Requirement already satisfied: msgpack>=0.5.0 in /Users/ito_masakuni/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from neovim) (0.5.6)
Requirement already satisfied: greenlet in /Users/ito_masakuni/.pyenv/versions/3.6.5/lib/python3.6/site-packages (from neovim) (0.4.13)
$ pip show neovim # Neovimの情報
Name: neovim
Version: 0.2.6
Summary: Python client to neovim # 本体じゃなくてクライアントだよ
Home-page: http://github.com/neovim/python-client
Author: Thiago de Arruda
Author-email: tpadilha84@gmail.com
License: Apache
Location: /Users/ito_masakuni/.pyenv/versions/3.6.5/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires: msgpack, greenlet
Required-by:

あとはgrepのためにthe silver searcherを入れておきます。名前がかっこよくて好きです。コマンドがAgでさらに好き。

$ brew install the_silver_searcher

Vimのよく使う設定

それではここからvimrcリニューアル後、よく使う設定を書いておきます。

リーダー

let mapleader = "\"

POSTDのこの記事を読んでリーダーは親指から近いスペースに落ち着きました。が、他のキーマップを再構築している現時点で、リーダーを使うことはほとんどありません。誰かおすすめ教えて下さい。

検索&置換

set ignorecase
set smartcase
set wrapscan
set incsearch
set inccommand=split

小文字だけの検索は大文字小文字を無視し、大文字で検索した場合無視しない、というわがまま設定です。あとinccommand=splitは置換のとき異常に便利です。インタラクティブに変更することができて、何か往年のSublime Textを見ているようでした。

こんな感じ。

タブ幅

set tabstop=4
set shiftwidth=4
set softtabstop=0
set expandtab
set smarttab
set shiftround

stack over flowによるとタブに半角スペースを使っているユーザーの方が年収が高いそうです。使わない手はない、ということでタブはexpandtabします。

ファイルのスプリット(と行結合)

nnoremap <silent> <S-j> :split<CR>
nnoremap <silent> <S-l> :vsplit<CR>
nnoremap <Bar> $:let pos = getpos(".")<CR>:join<CR>:call setpos('.', pos)<CR>

Shift+j、もしくはShift+lで現在のバッファをスプリットして開きます。全体的に下に意識を向けるときはj、右に意識を向けるときはlとしたいので、スプリットもこの設定で使っていますが、そうすると同じくらい多用するもともとのJ(行結合)を殺してしまいます。このため、バー(|)を行結合に再度割り当てています。まぁまぁ許せる。

ウィンドウ移動

nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

Ctrl+hjklでウィンドウ間の移動ができるようになります。この方法は僕が考えたわけではなく、誰かの発明ですが、モニタのサイズが大きくなってウィンドウをいくつも分割するのが普通となった昨今、最高の発明だと思います。

行移動

" 折り返し行移動
nnoremap j gj
nnoremap k gk
vnoremap j gj
vnoremap k gk

" 20行ずつ移動
nnoremap <C-n> 20j
vnoremap <C-n> 20j
nnoremap <C-p> 20k
vnoremap <C-p> 20k

ファイルはデフォルトでwrapするようにしてあるので、長い行だといちいちgjgjで移動しなければなりません。辛すぎるので前半4行は重要でした。またC-n、C-pで20行ずつ移動するように仕込んであります。デフォルトでC-d、C-uやC-f、C-bで画面内を飛ばし気味に移動できるのですが、ダウン、アップ、フォワード、バックと使うキーが何か似ていて混乱するので、いっそそれらを全部殺してちょうどいい行数を自分で設定することにしました。

ノーマルモード移行と保存

inoremap <silent> jj <ESC>:<C-u>w<CR>

インサートモードからjjで戻る。これは割と前から聞いていた手法なのですが、そこでさらに保存をするようにしています。僕はファイルを3秒ごとに保存する手癖がついていて、これまでひたすら :w を叩いていたりしたのですが、「インサートから戻るときがファイル変更のタイミングでは? それならいっそ保存しちゃえば?」と気づいて作ったマッピングです。超便利。

よく使うプラグインの設定

dein.vim

まずdein.vimを入れなければなりません。プラグインマネージャーです。dein.vimのQuick Startをそのままなぞります。cache配下にプラグインを設置することにあまり馴染みがなかったけど、まぁいっかという感じ。

$ curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh
$ sh ./installer.sh ~/.cache/dein

上記を動かすと、init.vimへ書くべき設定を表示してくれて親切です。基本的に、これをinit.vimへそのまま書きます。

"dein Scripts-----------------------------
if &compatible
  set nocompatible               " Be iMproved
endif

" Required:
set runtimepath+=/Users/ito_masakuni/.cache/dein/repos/github.com/Shougo/dein.vim

" Required:
if dein#load_state('/Users/ito_masakuni/.cache/dein')
  call dein#begin('/Users/ito_masakuni/.cache/dein')

  " Let dein manage dein
  " Required:
  call dein#add('/Users/ito_masakuni/.cache/dein/repos/github.com/Shougo/dein.vim')

  " Add or remove your plugins here:
  call dein#add('Shougo/neosnippet.vim')
  call dein#add('Shougo/neosnippet-snippets')

  " You can specify revision/branch/tag.
  call dein#add('Shougo/deol.nvim', { 'rev': '01203d4c9' })

  " Required:
  call dein#end()
  call dein#save_state()
endif

" Required:
filetype plugin indent on
syntax enable

" If you want to install not installed plugins on startup.
"if dein#check_install()
"  call dein#install()
"endif

"End dein Scripts-------------------------

必要であればプラグインの取捨選択をしてください。下記のように書くことでNeovimを起動したとき、プラグインのロードを行ってくれます。

call dein#add('Shougo/neosnippet.vim')

また下記の部分はコメントアウトされていますが、これを有効にするとNeovim起動時に必要なプラグインをインストールしてくれるようになります。便利ですね。ここから先のプラグインはすべてdein.vimを通して下記のようにインストールされています。

" If you want to install not installed plugins on startup.
"if dein#check_install()
"  call dein#install()
"endif

denite.vim

nnoremap [denite] <Nop>
nmap <C-d> [denite]


" grep
call denite#custom#var('grep', 'command', ['ag'])
call denite#custom#var('grep', 'default_opts', ['-i', '--vimgrep'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'pattern_opt', [])
call denite#custom#var('grep', 'separator', ['--'])
call denite#custom#var('grep', 'final_opts', [])

nnoremap <silent> [denite]<C-g> :<C-u>Denite grep -mode=normal<CR>
nnoremap <silent> [denite]<C-r> :<C-u>Denite -resume<CR>
nnoremap <silent> [denite]<C-n> :<C-u>Denite -resume -cursor-pos=+1 -immediately<CR>
nnoremap <silent> [denite]<C-p> :<C-u>Denite -resume -cursor-pos=-1 -immediately<CR>

" ノーマルモードで起動、jjでノーマルへ
call denite#custom#option('default', {'mode': 'normal'})
call denite#custom#map('insert', 'jj', '<denite:enter_mode:normal>')

" ファイル一覧
noremap [denite] :Denite file_rec -mode=insert
call denite#custom#var('file_rec', 'command', ['ag', '--follow', '--nocolor', '--nogroup', '-g', ''])
call denite#custom#var('file_rec', 'matchers', ['matcher_fuzzy', 'matcher_ignore_globs'])
call denite#custom#filter('matcher_ignore_globs', 'ignore_globs',
      \ ['.git/', '__pycache__/', '*.o', '*.make', '*.min.*'])

" ディレクトリ一覧
noremap [denite]<C-d> :<C-u>Denite directory_rec<CR>
noremap [denite]<C-c> :<C-u>Denite directory_rec -default-action=cd<CR>

" 移動
call denite#custom#map('normal', 'j', '<denite:nop>', 'noremap')
call denite#custom#map('normal', 'k', '<denite:nop>', 'noremap')
call denite#custom#map('normal', '<C-n>', '<denite:move_to_next_line>', 'noremap')
call denite#custom#map('insert', '<C-n>', '<denite:move_to_next_line>', 'noremap')
call denite#custom#map('normal', '<C-p>', '<denite:move_to_previous_line>', 'noremap')
call denite#custom#map('insert', '<C-p>', '<denite:move_to_previous_line>', 'noremap')
call denite#custom#map('normal', '<C-u>', '<denite:move_up_path>', 'noremap')
call denite#custom#map('insert', '<C-u>', '<denite:move_up_path>', 'noremap')

" ウィンドウを分割して開く
call denite#custom#map('normal', '<C-j>', '<denite:do_action:split>', 'noremap')
call denite#custom#map('insert', '<C-j>', '<denite:do_action:split>', 'noremap')
call denite#custom#map('normal', '<C-l>', '<denite:do_action:vsplit>', 'noremap')
call denite#custom#map('insert', '<C-l>', '<denite:do_action:vsplit>', 'noremap')

unite.vimの後継で、ファイルや文字列検索の統合インターフェースです。超使う。上記のような設定で、僕の場合は基本的にC-dがdeniteのはじまりです。

NERDTree

nnoremap <silent> <C-e> :NERDTreeToggle<CR>

" 表示幅
let g:NERDTreeWinSize=50

" ブックマークを表示
let g:NERDTreeShowBookmarks=1

" 親ディレクトリへ移動
let g:NERDTreeMapUpdir=''

" ファイルの開き方
let g:NERDTreeMapOpenSplit='<C-j>'
let g:NERDTreeMapOpenVSplit='<C-l>'

" ファイルを開いたらNERDTreeを閉じる
let g:NERDTreeQuitOnOpen=1

" 隠しファイルを表示
let g:NERDTreeShowHidden=1

" 非表示ファイル
let g:NERDTreeIgnore=['\.git$', '\.clean$', '\.swp$', '\.bak$', '\~$']

" NERDTreeを同時に閉じる
autocmd bufenter * if (winnr('$') == 1 && exists('b:NERDTree') && b:NERDTree.isTabTree()) | q | endif

ファイルビューワーです。これも超有名。というか超有名なところのプラグインしか使ってないんです。deniteでカレントディレクトリ配下超速検索ができるんですけど、ツリービューで見たいときはどうしてもあります。そういうときにこちらを利用します。

surround.vim

囲ってあるカッコやクォーテーションを操作します。このsurround.vimと合わせてvim-repeatも使うことをおすすめします。Vimは基本的にデフォルトの機能しかドットでリピートすることができません。vim-repeatを入れることによって、surround.vimのカッコの操作までリピートできるようになります。

operator-replace

map _ <Plug>(operator-replace)

たとえばヤンクした文字でダブルクォーテーション内の文字列を置換したいとき、「ヤンク>ダブルクォーテーション内削除>レジスタから貼り付け」みたいにやると思うんですけど、このプラグインを使うと、「ヤンク>ダブルクォーテーション内置換」ができるようになります。operator-userに依存しているので一緒に入れましょう。上記のようにしてアンダースコアに割り当てています。

deoplete.nvim

let g:deoplete#enable_at_startup = 1
let g:deoplete#auto_completion_start_length = 1

単語補完のフレームワークです。python3が必要になるため、前述のNeovimがインストールされていることをご確認ください。上記のような設定で補完を開始してくれます。

ただし、deoplete単体では真価は発揮されません。後述のdeopleteに対応したプラグインを別途インストールしてください。

jedi-vim / deoplete-jedi

let g:deoplete#sources#jedi#server_timeout=100
let g:deoplete#sources#jedi#statement_length=100
" jedi本体の補完は切る(deoplete-jediで非同期処理をしてくれるため)
let g:jedi#completions_enabled = 0

pythonの補完と静的解析のjedi、deopleteと連携するdeoplete-jediです。jediは宣言への移動などに使っています。deoplete-jediを合わせて使う理由は、jedi単体でも補完はできるのですが非同期で動いてくれないためです(numpyなどの巨大メソッドを補完するときに固まってしまいます)。deoplete-jediはdeoplete用に内部にjediを持っていて、非同期で補完をしてくれます。この方法が正しいかはイマイチ自信がありません。

phpcd

PHPの補完です。deopleteでPHPの補完にはdeoplete-padawanなどがあるのですが、上手く動かすことができず、これに落ち着きました。

僕のVim力をもっと強くする

載せたのは一部ですし、まだまだ改善点は鬼のようにあると思うんですが、少し手を入れるだけで感動を覚えるほどにVimが生まれ変わりました。このままNeovimに行くのか、はたまたVim本家へ戻るのかわかりませんけど、最近は「一日一Vim改善」と称して、init.vimへ手を入れるようにしています。これで少しでもVim力向上したい。もう一度力を貸してくれるかい。

今までよく分からない雑な設定でやってきたなかで、「それでもやってこれたのがVimのポテンシャル」と言えなくもないのですが、これがちゃんと設定したら、僕はどこまで強くなれるのか? ここから試していくことになるでしょう。フフフ、コードを書く前にプロジェクトを完結させてくれる……。今年はVimConf 2018にも行こうと思いました。人気らしいので行けるかわからないけど。

ところでこの記事もNeovimで書いているのですが、いろんなモジュールをインストール、アンインストールしながら書くのは非常に辛かったです。

それでは、バックエンドエンジニアのまさくにでした。