はじめに
私はC/C++開発にNeoVimを使用しています。
PythonなどはこのようなVimをIDEチックにみたいな記事がたくさんありますが、C/C++に関しては少ない気がしたので、今回私の使用している環境に関して記しておきます。
ツッコミ大歓迎です。
Colorschemeは
NeovimでモダンなPython環境を構築する
の記事を真似させていただきました。
システムセッティング
基本的にMac OS Xを前提として話を進めます。
私の使用している環境は、
・Mac OS X 13.06
・neovim 0.3.0
・fishshell 2.7.1
・python3.6.5(pyenv)
・python2.7.10(system標準)
という感じです。
プラグインマネージャーは暗黒美夢王さんのdein.vimを使用していて、tomlファイルでプラグイン管理しています。
この記事の設定を利用するためには、LLVM
が必要になります。
私は
$ brew install llvm --with-lldb --with-toolchain --with-python@2
でllvmを入れましたが、その際に少し引っかかったので、それはまた今度記事にします。
IDEって何があったっけ?
IDEにある機能は
・補完
・リンター
・デバッガー
・タグジャンプ
・検索機能
とかな気がします。
これらをそれぞれ説明していきます。
Let's IDE!!!
補完
deoplete.nvim
暗黒美夢王Shougoさんによる自動補完プラグイン。これがないと何も始まらない。
このプラグインの素晴らしさは、補完速度と拡張性です。
これは補完のプラットフォームであり(私の理解では)、各言語のソースを随時追加するという形で環境を作っていくイメージです。
[[plugins]]
repo = 'Shougo/deoplete.nvim'
depends = 'context_filetype.vim'
on_i = 1
hook_source = '''
call deoplete#enable()
" No display of the number of competion list
set shortmess+=c
" <TAB>: completion.
inoremap <silent><expr> <TAB>
\ pumvisible() ? "\<C-n>" :
\ <SID>check_back_space() ? "\<TAB>" :
\ deoplete#manual_complete()
function! s:check_back_space() abort
let col = col('.') - 1
return !col || getline('.')[col - 1] =~ '\s'
endfunction
" <S-TAB>: completion back.
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"
" <C-e>: popup cancel
inoremap <expr><C-e> deoplete#cancel_popup()
call deoplete#custom#source('_', 'matchers', ['matcher_head'])
call deoplete#custom#source('_', 'converters', [
\ 'converter_remove_paren',
\ 'converter_remove_overlap',
\ 'matcher_length',
\ 'converter_truncate_abbr',
\ 'converter_truncate_menu',
\ 'converter_auto_delimiter',
\ ])
call deoplete#custom#option('keyword_patterns', {
\ '_': '[a-zA-Z_]\k*\(?',
\ 'tex': '[^\w|\s][a-zA-Z_]\w*',
\ })
call deoplete#custom#option('camel_case', v:true)
'''
deoplete-clang, deoplete-clang2, deoplete-clangx
これらはC/C++用のdeopleteソースです。
deoplete-clang
とdeoplete-clang2
はjson compilation databaseに対応しているので、cmakeプロジェクトであればライブラリの補完をすることができます。
さらにdeoplete-clang
はCMakeLists.txt
でcmake関数などの補完も効くのでいいです。(deoplete-clang2はできるかわかりません)
deoplete-clangx
はjson compilation databaseに対応していませんが、速度的には一番早いです。
しかし、正直どれもちょっと重い感じはあります。
参考までに、deoplete-clang
の設定を書いておきます。基本的にはどれも設定項目は同じです。
詳しくはREADME.md
やdocumentを見てください。
[[plugins]]
repo = 'zchee/deoplete-clang'
on_ft = ['c', 'cpp', 'cmake']
hook_source = '''
let g:deoplete#sources#clang#libclang_path = system("llvm-config --prefix")[:-2] . '/lib/libclang.dylib'
let g:deoplete#sources#clang#clang_header = system("llvm-config --prefix")[:-2] . '/lib/clang'
let g:deoplete#sources#clang#sort_algo = 'priority'
let g:deoplete#sources#clang#clang_complete_database="./build/"
'''
depends = ['deoplete.nvim']
参考: deoplete-clangで快適C++エディット!!!
neoinclude.vim
上記のc/c++ソースは、ヘッダファイルの補完をしてくれないので、このプラグインを入れることでヘッダ補完ができるようになります。
[[plugins]]
repo = 'Shougo/neoinclude.vim'
on_ft = ['c', 'cpp']
depends = ['deoplete.nvim']
リンター
ale
非同期でエラーチェックをしてくれる素晴らしいプラグイン。
c/c++では
・clang
・clangcheck
・clangtidy !!
・clang-format
・cppcheck
・cpplint !!
・cquery
・flawfinder
・gcc
これらの選択肢があります。
私が使用したことあるものは、clang, clangcheck, clangtidyです。
clangは書いてる途中でエラーチェックをしてくれますが、CPUが唸りを上げます(笑)
clangcheckとclangtidyは、CPUに優しいですが、保存時のみしかチェックしてくれません。
CPUには申し訳ないですが、やっぱり書いてる途中でエラー表示して欲しいので、clangを使ってます。
[[plugins]]
repo = 'w0rp/ale'
hook_add = '''
" シンボルカラムを表示したままにする
let g:ale_sign_column_always = 1
" 保存時に整形してくれる
let g:ale_fix_on_save = 1
" 補完してくれる
let g:ale_completion_enabled = 1
" エラー行に表示するマーク
let g:ale_sign_error = '⨉'
let g:ale_sign_warning = '⚠'
" エラー行にカーソルをあわせた際に表示されるメッセージフォーマット
let g:ale_echo_msg_format = '[%linter%] %s [%severity%]'
let g:ale_statusline_format = ['⨉ %d', '⚠ %d', '⬥ ok']
let g:ale_linters = {
\ 'c' : ['clangd'],
\ 'cpp' : ['clangd']
\}
'''
デバッガー
IDEには絶対デバッガーが付いていますが、Vimでもそれを実現してくれる素晴らしいプラグインがあります。
lldb.nvim
これは本当に素晴らしいです。IDEみたいになります(笑)
リポジトリにYoutubeでの使い方動画があったので、参考にしてください。
ただ、Macでこれを使おうとすると引っかかります。
私はpyenvとpyenv-virtualenvで2系と3系それぞれのneovim用環境を使っていたのですが、システムの標準python(/usr/bin/python2.7)しかlldbモジュールをimportできないという部分でめちゃめちゃ引っかかりました。
ベストな解決策は未だにわからないのですが、私は
・python2 => システム標準
・python3 => pyenv + pyenv-virtualenv
で変えることにしました。
手順としては、
1.標準pythonにneovimモジュールをインストールする。
$ sudo /System/Library/Frameworks/Python.framework/Versions/2.7/bin/python -m easy_install neovim
2.PYTHONPATHにlldbの場所を指定する
$ echo 'export PYTHONPATH="/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python:${PYTHONPATH}"' > ~/.bash_profile
3.neovimのg:python_host_prog
に/usr/bin/python2.7を指定する
let g:python_host_prog = '/usr/bin/python2.7'
let g:python3_host_prog = $HOME . '/.pyenv/versions/neovim3/bin/python'
という手順です。
設定は特にはしていません。
[[plugins]]
repo = 'dbgx/lldb.nvim'
on_ft = ['c', 'cpp']
しかし、lldb.nvimはもうメンテナンスされていません!!!
理由はhttps://github.com/dbgx/lldb.nvim/issues/51#issuecomment-294894345
に書いてあります。
したがって、いつ使えなくなるかわからないので、このプラグインを使うことに関して責任は持てません(笑)
使えなくなった時のために、同じようなプラグインを載せておきます。
・neogdb
・nvim-gdb
どちらもgdb
が必要で、
$ brew install gdb
でインストールできます。
参考:neovim + clang + lldbでC++開発環境構築メモ(自動補完、文法チェック、デバッガフロントエンド)
タグジャンプ
タグジャンプは有名どころだと、ctags
やgtags
などがありますが、C++は文法の複雑さ的にこれらだと対応しきれないらしいです。(C++で実際に使ったことないのでほんとかはわかりません)
そこで私はrtagsを使用しています。
rtagsはコンパイラがソースコード解析を行うので、ミスが起こりませんし、使い心地が非常にいいです。
インストールはhomebrewで
$ brew install rtags
とやれば入ります。
ただ、dependencyでllvm
があり、rtagsのdependencyで入れた場合に--with-lldb --with-python@2 --with-toolchain
が適用されるかわからなかったので、私はllvmを最初にインストールした後、rtagsをインストールしました。
vim-rtags
これはvimでrtagsを使えるようにしたプラグインです。
rdmを自動で起動してくれて、compile_commands.jsonを自動検出しrcに渡してくれるので、特に何も考えずにVimでrtagsを使うことができます。
[[plugins]]
repo = 'lyuts/vim-rtags'
on_ft = ['c', 'cpp']
hook_add = '''
let g:rtagsUseDefaultMappings = 0
let g:rtagsAutoLaunchRdm = 1
'''
hook_source = '''
noremap <leader>ri :call rtags#SymbolInfo()<CR>
noremap <Leader>rj :call rtags#JumpTo(g:SAME_WINDOW)<CR>
noremap <Leader>rJ :call rtags#JumpTo(g:SAME_WINDOW, { '--declaration-only' : '' })<CR>
noremap <Leader>rS :call rtags#JumpTo(g:H_SPLIT)<CR>
noremap <Leader>rV :call rtags#JumpTo(g:V_SPLIT)<CR>
noremap <Leader>rT :call rtags#JumpTo(g:NEW_TAB)<CR>
noremap <Leader>rp :call rtags#JumpToParent()<CR>
noremap <Leader>rf :call rtags#FindRefs()<CR>
noremap <Leader>rF :call rtags#FindRefsCallTree()<CR>
noremap <Leader>rn :call rtags#FindRefsByName(input("Pattern? ", "", "customlist,rtags#CompleteSymbols"))<CR>
noremap <Leader>rs :call rtags#FindSymbols(input("Pattern? ", "", "customlist,rtags#CompleteSymbols"))<CR>
noremap <Leader>rr :call rtags#ReindexFile()<CR>
noremap <Leader>rl :call rtags#ProjectList()<CR>
noremap <Leader>rw :call rtags#RenameSymbolUnderCursor()<CR>
noremap <Leader>rv :call rtags#FindVirtuals()<CR>
noremap <Leader>rb :call rtags#JumpBack()<CR>
noremap <Leader>rC :call rtags#FindSuperClasses()<CR>
noremap <Leader>rc :call rtags#FindSubClasses()<CR>
noremap <Leader>rd :call rtags#Diagnostics()<CR>
'''
参考: 最強のC/C++インデクサー "Rtags" を本気で使う
検索機能
denite.nvim
これまた暗黒美夢王様の素晴らしいプラグインです。
私は全然使いこなせていませんが、完璧に使いこなせれば神になれると勝手に思っています。
[[plugins]]
repo = 'Shougo/denite.nvim'
hook_add = '''
nmap <Space> [denite]
nmap <Space>p [deniteProject]
nmap <silent> [denite]b :<C-u>Denite buffer<CR>
nmap <silent> [denite]g :<C-u>Denite grep<CR>
nmap <silent> [denite]m :<C-u>Denite file_mru<CR> <silent> <C-u><C-y> :<C-u>Denite neoyank<CR>
nmap <silent> [denite]f :<C-u>Denite file/rec<CR>
nmap <silent> [denite]d :<C-u>Denite directory_rec<CR>
nmap <silent> [deniteProject]b :<C-u>DeniteProject buffer<CR>
nmap <silent> [deniteProject]g :<C-u>DeniteProject grep<CR>
nmap <silent> [deniteProject]m :<C-u>DeniteProject file_mru<CR> <silent> <C-u><C-y> :<C-u>Denite neoyank<CR>
nmap <silent> [deniteProject]f :<C-u>DeniteProject file/rec<CR>
nmap <silent> [deniteProject]d :<C-u>DeniteProject directory_rec<CR>
'''
hook_source = '''
if executable('rg')
call denite#custom#var('file/rec', 'command', ['rg', '--files', '--glob', '!.git'])
call denite#custom#var('file_rec', 'command', ['rg', '--files', '--glob', '!.git'])
call denite#custom#var('grep', 'command', ['rg', '--threads', '1'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'final_opts', [])
call denite#custom#var('grep', 'separator', ['--'])
call denite#custom#var('grep', 'default_opts', ['--vimgrep', '--no-heading'])
endif
call denite#custom#map('insert', "<C-n>", '<denite:move_to_next_line>')
call denite#custom#map('insert', "<C-p>", '<denite:move_to_previous_line>')
call denite#custom#map('insert', "<C-t>", '<denite:do_action:tabopen>')
call denite#custom#map('insert', "<C-v>", '<denite:do_action:vsplit>')
call denite#custom#map('insert', "<C-h>", '<denite:do_action:split>')
call denite#custom#filter('matcher_ignore_globs', 'ignore_globs', [
\ '.git/', 'build/', '__pycache__/',
\ 'images/', '*.o', '*.make', '*.min.*',
\ 'img/', 'fonts/', '*~', '*.exe', '*.bak',
\ '.DS_Store', '*.pyc', '*.class', 'tags'
\ ])
'''
その他
ここからはメインではないですが、あるといいプラグインを紹介していきます。
a.vim
これによりヘッダファイルとソースファイルを簡単に行き来することができます。
[[plugins]]
repo = 'vim-scripts/a.vim'
on_ft = ['c', 'cpp']
hook_source = '''
nmap <silent> <leader>aa :A<CR>
nmap <silent> <leader>at :AT<CR>
nmap <silent> <leader>av :AV<CR>
'''
vim-cpp
cpp11用のシンタックスプラグインです。
DoxygenToolkit.vim
Doxygen用のコメントを挿入することができるプラグインです。
[[plugins]]
repo = 'vim-scripts/DoxygenToolkit.vim'
on_ft = ['c', 'cpp']
hook_source = '''
nmap <leader>dox :Dox<CR>
'''
vim-cmake
vimからcmakeコマンドを実行できるプラグインです。
自分はtmuxで分割して下のペインでビルドコマンドを打つのであんまり使ってないです。
neosnippet
暗黒美夢王によるスニペットのプラグイン。めちゃ便利。
後続のdeoppetの完成が非常に楽しみ。
[[plugins]]
repo = 'Shougo/neosnippet'
on_i = 1
hook_source = '''
imap <C-k> <Plug>(neosnippet_expand_or_jump)
smap <C-k> <Plug>(neosnippet_expand_or_jump)
xmap <C-k> <Plug>(neosnippet_expand_target)
if has('conceal')
set conceallevel=2 concealcursor=niv
endif
let g:neosnippet#enable_snipmate_compatibility = 1
let g:neosnippet#enable_completed_snippet = 1
let g:neosnippet#expand_word_boundary = 1
let g:neosnippet#enable_completed_snippet = 1
'''
hook_add = '''
let g:neosnippet#snippets_directory = "$XDG_CONFIG_HOME/nvim/autoload/snippets/"
'''
nerdtree
ファイラーです。
これを選んだ理由は、有名だから(笑)
他にもっといいものがあれば乗り換えます。
Vimfiler
との違いって何ですか?
暗黒美夢王のdefx.nvimがリリースしたら試してみようと思う。
[[plugins]]
repo = 'scrooloose/nerdtree'
hook_add = '''
" 隠しファイルを設定する
let NERDTreeShowHidden = 1
'''
vim-nerdtree-tabs
全タブで共通のnerdtreeを表示してくれます。
自分はタブをよく使うので、結構便利。
[[plugins]]
repo = 'jistr/vim-nerdtree-tabs'
hook_add = '''
" トグル設定
map <C-e> <plug>NERDTreeTabsToggle<CR>
" 起動時有効
let g:nerdtree_tabs_open_on_console_startup=1
" 起動時にファイルにカーソルを合わせる
function! s:MoveCursorAtStart()
call feedkeys("\<C-l>")
endfunction
autocmd VimEnter * NERDTree | call s:MoveCursorAtStart()
'''
depends = ['nerdtree']
vim-airline
ステータスラインを表示してくれます。
vimがかっこよくなる。
[[plugins]]
repo = 'vim-airline/vim-airline'
hook_add = '''
" タブラインの表示
let g:airline#extensions#tabline#enabled = 1
" (タブが一個の場合)バッファのリストをタブラインに表示する機能をオフ
let g:airline#extensions#tabline#show_buffers = 0
" 0でそのタブで開いてるウィンドウ数、1で左のタブから連番
let g:airline#extensions#tabline#tab_nr_type = 1
" パワーラインフォントの使用
let g:airline_powerline_fonts = 1
" aleの表示
let g:airline#extensions#ale#enabled = 1
" Do not collapse the status line while having multiple windows
let g:airline_inactive_collapse = 0
" tagbarの表示
let g:airline#extensions#tagbar#enabled = 1
" virtualenvを有効
let g:airline#extensions#virtualenv#enabled = 1
'''
tagbar
クラスのメンバやメソッドの一覧を見れる。
あと今どのメソッドにいるのかわかる。
vim-fugitive
vimでgitを使える。マジ便利。
vim-gitgutter
変更箇所に印をつけてくれる。ちょっとラグがある。
こんな感じです。
ただ羅列してしまった。
Language Serverという選択肢
ぶっちゃけ自分はこれを使ってます!!
Language Serverって何ができるの?
Language Server Protocolについて知らない方は下の記事を参照してください。
参考:language server protocolについて (前編)
ざっくり言うと、これ一つで補完・リンター・参照とか全部できちゃう感じです。
deoplete-clang*
とかale
のC/C++のリンターとかは、やっぱりちょっと動作がもたつきます。
pythonとかのソースと比べると、使いづらい印象です。
それに比べて、LSPはまじで早い!
補完の速度もリンターの速度も尋常じゃないほど早いです。
現状C/C++においてはLSPを使うのが最善の選択肢だと勝手に思ってます。
もちろんcompilation databaseにも対応しているので、ライブラリ補完などもソッコー効きます。
LSPのプラグイン
LanguageClient-neovim
他にもvim-lspとかありますが、deoplete
対応しているのでこれを使ってます。
[[plugins]]
repo = 'autozimu/LanguageClient-neovim'
build = ['./install.sh']
hook_add = '''
let s:node = empty($XDG_CONFIG_HOME) ? expand('$HOME/.config') : $XDG_CONFIG_HOME
let g:LanguageClient_serverCommands = {
\ 'c': ['clangd', '-compile-commands-dir=' . getcwd() . '/build'],
\ 'cpp': ['clangd', '-compile-commands-dir=' . getcwd() . '/build'],
\ }
" not stop completion $ & /
set hidden
set signcolumn=yes
let g:LanguageClient_hoverPreview = "Never"
nnoremap <F5> :call LanguageClient_contextMenu()<CR>
" Or map each action separately
nnoremap <silent> K :call LanguageClient#textDocument_hover()<CR>
nnoremap <silent> gd :call LanguageClient#textDocument_definition()<CR>
nnoremap <silent> <F2> :call LanguageClient#textDocument_rename()<CR>
'''
私はcmakeのプロジェクトがメインなので、compile_commands.jsonがある前提でvimの設定をしています。
じゃあ結局お前は何を使ってるんだ!
現状私は
補完
deoplete.nvimをベースにしてLanguageClient-neovim
リンター
LanguageClient-neovim
デバッガー
lldb.nvim
検索
denite.nvim
タグジャンプ
vim-rtags
を使って開発しています。
renameに関しては、vim-rtags
でもLanguageClient-neovim
でもできますが、renameした後やっぱ戻したい!ってなった時に、vim-rtags
はReDo一回で1つのシンボルしかReDoしてくれません。
LanguageClient-neovim
はReDo一発で全シンボルをReDoしてくれるので、そちらを採用しています。
ヘッダファイル内でエラー吐きまくり
私がLSPを使用している際に、ソースファイルではエラーも出ずに補完もちゃんと効くのに、ヘッダファイルではエラー吐きまくり&補完きかない問題がありました。
そこで、LanguageClient-neovimのissuesで聞いてみたところ、compile_commands.jsonがソースファイルに関するコンパイルコマンドしか記述していないからだという返答が返ってきました。
https://github.com/autozimu/LanguageClient-neovim/issues/495
回答者さんは非常に優しく、解決策をちゃんと提示してくれました。
compdbと言うpythonモジュールを使うと、ヘッダファイルの情報も付与したcompile_commands.jsonを生成してくれるので、ヘッダファイル内でもエラーをはかなくなります。
cmakeで生成されて./build/compile_commands.json
を元に、compdbで./compile_commands.json
を作成します。
これを各種プラグインに読み込ませてあげればいいので、LanguageClient-neovimの設定を次のように変更します。
let g:LanguageClient_serverCommands = {
\ 'c': ['clangd', '-compile-commands-dir=' . getcwd()],
\ 'cpp': ['clangd', '-compile-commands-dir=' . getcwd()],
\ }
これで快適に使用することができます。
ちなみにこれを一気にやる関数をfish
で書いたので、参考までに貼っておきます。
./build
内で実行してください。
function build
if test -x (command -v clang)
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
if test -x (command -v compdb)
compdb list > ../compile_commands.json
else
echo "\e[33mWarning: compdb is not installed"
end
else
cmake ..
echo "\e[33mWarning: clang is not installed, using default compiler"
end
end
Help me!
今まで頑張って環境構築を続けて来たのですが、3つほどできていないorわからない部分があるので、どなたかわかる方いらしたら是非教えていただきたいです。
1.ヘッダファイルの中身をソースファイルに展開
CLionであった機能なんですが、
class Hoge
{
public:
int hoge();
}
とあるものを
#include "hoge.hpp"
int Hoge::hoge()
{
}
と展開したいです。
2.ライブラリのヘッダファイル補完
上でneoinclude
を用いればヘッダファイルの補完ができると言いましたが、cmakeプロジェクトにおけるライブラリのヘッダファイルは補完が効きません。
自分はこうしてる!みたいなものがある方がいたら是非教えてください。
3.新規ファイル作成
新規ファイルを目的の場所にソッコー作れる方法を探しています。
自分は基本プロジェクトルートでVimを起動し、そこから動くことはないので、denite
で新規ファイルを作成するとフルパスで記述しないといけません...
なので今はnerdtree
から追加するというナンセンスな方法を取っています。
ファイル生成の簡単な方法があったら是非教えてください。
2018/07/19 追記
denite
で簡単にできました。無知な自分が恥ずかしい....
:Denite file
でDenite Windowが開くので、Directory絞ってファイル名打てばできました。
最後に
めっっっっっっっっちゃ長くなりましたが、読んでいただきありがとうございました。
この記事がどなたかの助けになれば幸いです。
ツッコミ&質問&アドバイスお待ちしております。