LoginSignup
110
105

More than 3 years have passed since last update.

NeovimでC/C++のIDE(っぽい)環境を構築する

Last updated at Posted at 2018-07-13

はじめに

私はC/C++開発にNeoVimを使用しています。
PythonなどはこのようなVimをIDEチックにみたいな記事がたくさんありますが、C/C++に関しては少ない気がしたので、今回私の使用している環境に関して記しておきます。
ツッコミ大歓迎です

ちなみに現在の私の環境はこんな感じです。
スクリーンショット 2018-07-13 11.05.05.png

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-clangdeoplete-clang2はjson compilation databaseに対応しているので、cmakeプロジェクトであればライブラリの補完をすることができます。
さらにdeoplete-clangCMakeLists.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を指定する

init.vim
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++開発環境構築メモ(自動補完、文法チェック、デバッガフロントエンド)

タグジャンプ

タグジャンプは有名どころだと、ctagsgtagsなどがありますが、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であった機能なんですが、

Hoge.hpp
class Hoge
{
public:
    int hoge();
}

とあるものを

Hoge.cpp
#include "hoge.hpp"

int Hoge::hoge()
{

}

と展開したいです。

2.ライブラリのヘッダファイル補完

上でneoincludeを用いればヘッダファイルの補完ができると言いましたが、cmakeプロジェクトにおけるライブラリのヘッダファイルは補完が効きません。
自分はこうしてる!みたいなものがある方がいたら是非教えてください。

3.新規ファイル作成

新規ファイルを目的の場所にソッコー作れる方法を探しています。
自分は基本プロジェクトルートでVimを起動し、そこから動くことはないので、deniteで新規ファイルを作成するとフルパスで記述しないといけません...
なので今はnerdtreeから追加するというナンセンスな方法を取っています。

ファイル生成の簡単な方法があったら是非教えてください。

2018/07/19 追記
deniteで簡単にできました。無知な自分が恥ずかしい....

:Denite file

でDenite Windowが開くので、Directory絞ってファイル名打てばできました。

最後に

めっっっっっっっっちゃ長くなりましたが、読んでいただきありがとうございました。
この記事がどなたかの助けになれば幸いです。
ツッコミ&質問&アドバイスお待ちしております。

110
105
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
110
105