💻

Rails アプリを開発するための Neovim 設定

2024/05/15に公開

こんにちは、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)は solargraphrubocop を使用しています。
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

デモ

以下のことを行なっています。

  1. 実装ファイル → テストファイルを横に開く
  2. テストファイル → 実装ファイルを横に開く
  3. 実装ファイル → テストファイルを縦に開く
  4. テストファイル → 実装ファイルを縦に開く

まとめ

Neovim を使って Rails アプリの開発をスムーズにするためのいくつかの便利な設定とプラグインを紹介しました。
Neovim のカスタマイズが好きな方や、開発を効率化したい方にとって、ご参考になれば幸いです。

SocialPLUS Tech Blog

Discussion