Rails アプリを開発するための Neovim 設定
こんにちは、masaki です。
皆さんはどんなエディタを使って開発していますか?
私は Neovim を愛用しています。
多くの方が VSCode を使用していると思いますが、この記事では Vim や Neovim を使って Rails アプリの開発をしている方々のために、私の設定や便利な機能をご紹介します。
ちなみに、私の所属するソーシャルPLUS のバックエンドエンジニアが使っているエディタは以下の通りです。やはり VSCode が人気ですね。
- VSCode: 6人
- Vim/Neovim: 2人
- RubyMine: 1人
この記事の対象者
- Neovim で Rails アプリを開発をしている人
- Neovim のカスタマイズが好きな人
Neovim を使っている理由
私が Neovim を使っている理由は以下のとおりです。
- マウスを使用しないで済む
- シンプルなインターフェース
- Vim のキー操作が手に馴染んでいる
時折、VSCode への乗り換えを検討しますが、結局 Neovim に戻ってきてしまいます。
Neovim の設定
私が Neovim で Rails アプリを開発するときによく使っている機能(設定)を3つ紹介します。
- LSP 設定
- マルチカーソル
- テストファイルまたは実装ファイルを開く
私は Neovim のバージョン v0.9.5 を使用しています。
Vim での動作検証はしていませんが、Neovim 特有の設定はしていないためおそらく問題なく動作するはずです。
LSP 設定
LSP(Language Server Protocol)は solargraph
と rubocop
を使用しています。
LSP を使うための Neovim プラグインには coc.nvim
を使い、あいまい検索のために fzf.vim
( + fzf
)、coc-fzf
を使っています。
各ライブラリやプラグインの詳細についてはそれぞれのレポジトリをご覧ください。
定義ジャンプ、参照ジャンプ
定義ジャンプや参照ジャンプはかなりよく使うため、少しカスタマイズしています。
参照元にジャンプするとき、あいまい検索して開く対象を選択できると便利なので、fzf
を使って実現しています。
定義元は1箇所なので定義ジャンプをプレビューで開かなくてもいいのですが、参照ジャンプと併せて定義ジャンプもプレビューで開くようにしています。
また、プレビューしたあとファイルを水平分割(split
)するのか垂直分割(vsplit
)するのかを選択できるようにしています。
-
~/.config/nvim/init.vim
の設定
" 定義ジャンプ
nmap gd :<C-u>call CocAction('jumpDefinition', v:false)<CR>
nmap gr :<C-u>call CocAction('jumpReferences', v:false)<CR>
" coc-fzf の設定
let g:coc_fzf_opts = [
\ '--expect=ctrl-v,ctrl-s,ctrl-e',
\ '--bind=ctrl-n:preview-down,ctrl-p:preview-up',
\ ]
let g:fzf_action = {
\ 'ctrl-v': 'vsplit',
\ 'ctrl-s': 'split',
\ 'ctrl-e': 'edit'
\ }
デモ
定義元ジャンプで垂直分割する
参照元ジャンプで水平分割する
フォーマット
以前は solargraph
経由で rubocop
を使ってフォーマットしていましたが、rubocop
が LSP に対応し solargraph
の経由が不要になったため、かなり軽快に動作するようになりました。
-
~/.config/nvim/init.vim
の設定
nmap <space>f :call CocAction('format')<cr>
-
coc-settings.json
の設定
{
"languageserver": {
"rubocop": {
"command": "rubocop",
"args": ["--lsp"],
"filetypes": ["ruby"]
}
}
}
デモ
マルチカーソル
Neovim で置換もできますが、マルチカーソルのほうが早いシーンが多いのでマルチカーソルを使っています。
マルチカーソルは vim-visual-multi
プラグインを使っています。
スネークケース、パスカルケースへの変換のために vim-surround
プラグインも使っています。
以下はカスタマイズした私のキーマップ設定です。
-
~/.config/nvim/init.vim
の設定
let g:VM_maps = {}
let g:VM_maps["Align"] = '<M-a>' " 縦列を揃える
let g:VM_maps["Surround"] = 'S' " 囲み文字(" や ' など)で囲む
let g:VM_maps["Case Conversion Menu"] = 'C' " スネークケース、パスカルケースなどへの変換、`vim-visual-multi` プラグインが必要
let g:VM_maps["Add Cursor Down"] = '<M-Down>' " 下移動しながらカーソル選択
let g:VM_maps["Add Cursor Up"] = '<M-Up>' " 上移動しながらカーソル選択
デモ
テストファイルまたは実装ファイルを開く
実装ファイルに対応するテストファイルを開く、テストファイルに対応する実装ファイルを開く、という操作をよくするので、カスタムスクリプトにしています。
実装に対して spec ファイルを分割している場合などを考慮し、複数の候補の中から選択できるように
fzf.vim
( + fzf
)を使っています。
-
~/.config/nvim/init.vim
の設定
nnoremap <leader>ot :OpenTargetFile<CR>
command! OpenTargetFile call OpenTargetFile()
function! OpenTargetFile()
let target_path=''
let path=expand('%')
if path =~ '^app/'
if path =~ '^app/controllers/'
let target_path=substitute(substitute(expand('%'), '^app/controllers/', 'spec/', ''), '\v(.+)_controller.rb', '\1_spec.rb', '')
else
let target_path=substitute(substitute(expand('%'), '^app/', 'spec/', ''), '\v(.+).rb', '\1_spec.rb', '')
endif
elseif path =~ '^lib/'
let target_path=substitute(substitute(expand('%'), '^lib/', 'spec/lib/', ''), '\v(.+).rb', '\1_spec.rb', '')
elseif path =~ '^spec/'
if path =~ '^spec/requests/'
let target_path=substitute(substitute(expand('%'), '^spec/requests/', 'app/', ''), '\v(.+)\/.+_spec.rb', '\1.rb', '')
elseif path =~ '^spec/lib/'
let target_path=substitute(substitute(expand('%'), '^spec/lib/', 'lib/', ''), '\v(.+)_spec.rb', '\1.rb', '')
else
let target_path=substitute(substitute(expand('%'), '^spec/', 'app/', ''), '\v(.+)_spec.rb', '\1.rb', '')
endif
endif
if target_path == ''
echom 'no target file'
return
endif
call OpenFiles()
call feedkeys(target_path)
endfunction
function! OpenFiles(...)
let l:path = get(a:, 1, "")
let l:sort = get(a:, 2, "")
if l:path == ""
let l:path = "--sortr modified"
endif
call fzf#run(fzf#vim#with_preview(fzf#wrap({
\ 'source': printf("rg --hidden --files --glob '!**/.git/**' %s %s", l:sort, l:path),
\ 'sink*': function('s:open_files'),
\ 'options': [
\ '--prompt', 'Files> ',
\ '--multi',
\ '--expect=ctrl-v,ctrl-s,enter,ctrl-a,ctrl-e,ctrl-x',
\ '--bind=ctrl-a:select-all,ctrl-u:toggle,?:toggle-preview,ctrl-n:preview-down,ctrl-p:preview-up'
\ ]
\ })))
endfunction
function! s:open_files(lines)
if len(a:lines) < 2 | return | endif
let cmd = get(
\ {
\ 'ctrl-e': 'edit ',
\ 'ctrl-v': 'vertical split ',
\ 'ctrl-s': 'split '
\ },
\ a:lines[0], 'tab drop ')
for file in a:lines[1:]
let escaped_file = substitute(file, " ", "\\\\ ", 'g')
exec cmd . escaped_file
endfor
endfunction
デモ
以下のことを行なっています。
- 実装ファイル → テストファイルを横に開く
- テストファイル → 実装ファイルを横に開く
- 実装ファイル → テストファイルを縦に開く
- テストファイル → 実装ファイルを縦に開く
まとめ
Neovim を使って Rails アプリの開発をスムーズにするためのいくつかの便利な設定とプラグインを紹介しました。
Neovim のカスタマイズが好きな方や、開発を効率化したい方にとって、ご参考になれば幸いです。
Discussion