Skip to main content
  1. Posts/

Setting up Neovim for C++ Development with LSP

··1254 words·6 mins·
Table of Contents
update log
  • 2022-02-08: update fastgit URL

In this post, I would like to share how to set up Neovim for writing simple C++ programs.

Prerequisite
#

First, we need to install additional packages or tools. The installation sections below are mostly aimed at Linux. For macOS, clang is already installed, and for ccls (a LSP server for C++/C), you can simply install it via HomeBrew:

brew install ccls

Install gcc
#

LLVM requires gcc to be at least 5.1, first, we need to update gcc.

Install gcc-7 on CentOS 7
#

# How to do it on Ubuntu
sudo yum install centos-release-scl
sudo yum install devtoolset-7-gcc*
scl enable devtoolset-7 bash
which gcc
gcc --version

Ref:

Install gcc-7 on Ubuntu 16.04
#

Install a newer version of GCC since it is required to compile ccls1. On Ubuntu, you can install newer gcc via the following command:

sudo apt-get install -y software-properties-common
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt update
sudo apt install g++-7 -y

Set up gcc-7 to be the default:

update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 --slave /usr/bin/g++ g++ /usr/bin/g++-7
sudo update-alternatives --config gcc

Ref:

Install CMake
#

Ccls requires newer version of cmake. The cmake on my system is tool old. First, we need to download cmake binary release:

wget https://hub.fastgit.xyz/Kitware/CMake/releases/download/v3.20.1/cmake-3.20.1-linux-x86_64.sh
mkdir $HOME/tools/cmake
bash cmake-3.18.4-Linux-x86_64.sh --prefix=$HOME/tools/cmake --exclude-subdir --skip-license

Add cmake executable to PATH:

export PATH="$HOME/tools/cmake/bin:$PATH"

Ref:

Install llvm and clang
#

To install clang, either build it from source or install the binary release if it is available for your system.

Build from source
#

Follow the guide here on building Clang and LLVM on your platform.

git clone --depth=1 https://hub.fastgit.xyz/llvm/llvm-project.git
mkdir -p llvm-project/build
cd llvm-project/build
cmake -G "Unix Makefiles" -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DCMAKE_INSTALL_PREFIX=~/tools/llvm -DCMAKE_BUILD_TYPE=Release ../llvm

# Note that make -j may error out due to excessive memory usage, so we restrict
# the number of processor used to compile llvm.
make -j 16

The option -DCMAKE_INSTALL_PREFIX specify where we want to install llvm and clang. Since I do not have root rights, I install it under $HOME/tools/llvm. You may change it to other directory. We add clang-tools-extra to option DLLVM_ENABLE_PROJECTS so that extra tools like clangd and clang-tidy can also be built.

Install binary release
#

Note that for some systems, clang also has pre-built binary so you do not need to build from source yourself, see here.

For example, there is binary release for Ubuntu 16.04:

wget https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
tar xvf clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz

Do not forget to add the binary and include path to your PATH and LD_LIBRARY_PATH env variable.

ccls
#

ccls is a Language Server implementation for C++/C etc. It can be used for C++ code completion, linting, formatting etc. Ccls build instruction can be found here.

Build ccls
#

With all its dependencies installed, we can now build ccls:

git clone --depth=1 --recursive https://github.com/MaskRay/ccls
cd ccls
cmake -H. -BRelease -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$HOME/tools/clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-16.04
cmake --build Release

If you see the following error:

src/utils.hh:18:20: fatal error: optional: No such file or directory

This is because ccls can not find the optional header files. Your gcc version is too old, you should install gcc-7. See above section on how to install gcc-7 on Ubuntu.

Another error I met is that libtinfo is not found when compiling:

/usr/bin/ld: cannot find -ltinfo

Just install libtinfo-dev using apt:

sudo apt install libtinfo-dev

After all these steps, ccls should be compiled successfully. Add the executable directory to your PATH.

Ref:

Neovim config
#

Now comes to the configuration for Neovim.

Auto-completion
#

For auto-completion, we use vim-lsp, together with deoplete-vim-lsp and deoplete.

An example config is shown below:

Plug 'prabirshrestha/vim-lsp'

Plug 'Shougo/deoplete.nvim'
Plug 'lighttiger2505/deoplete-vim-lsp'

" setting with vim-lsp
if executable('ccls')
   au User lsp_setup call lsp#register_server({
      \ 'name': 'ccls',
      \ 'cmd': {server_info->['ccls']},
      \ 'root_uri': {server_info->lsp#utils#path_to_uri(
      \   lsp#utils#find_nearest_parent_file_directory(
      \     lsp#utils#get_buffer_path(), ['.ccls', 'compile_commands.json', '.git/']))},
      \ 'initialization_options': {
      \   'highlight': { 'lsRanges' : v:true },
      \   'cache': {'directory': stdpath('cache') . '/ccls' },
      \ },
      \ 'whitelist': ['c', 'cpp', 'objc', 'objcpp', 'cc'],
      \ })
endif

If you are on Linux, the above config should work as expected. As soon as you started editing C++ source files, code auto-completion for standard C++ header and for methods/class in standard library should work.

However, if we only use the above config, auto-completion only works for standard C++ libraries, since ccls does not know where to find the header file for other packages we use. We can create a .ccls under the project root to tell ccls our compilation flags. An example config for a source file using OpenCV is like the following:

clang
%h -x c++-header
-Wall
-Wextra
%cpp -std=c++11
%c -std=c11
-I/home/jdhao/local/include/opencv4
-I.

On macOS, I have encountered completion issues even for standard libraries. It seems that clang can not find the correct directory for the header files of standard libraries. The following is a working .ccls file:

clang
-isystem/Library/Developer/CommandLineTools/usr/include/c++/v1
%cpp -std=c++11

The directory /Library/Developer/CommandLineTools/usr/include/c++/v1 is where macOS stores the standard C++ header files. It uses the system clang. If you do not know where that directory is, use the following command:

clang -v -fsyntax-only -x c++ /dev/null

Some of the output will show the possible directories where standard C++ may exist:

#include <...> search starts here:
 /usr/local/include
 /Library/Developer/CommandLineTools/usr/bin/../include/c++/v1
 /Library/Developer/CommandLineTools/usr/lib/clang/11.0.0/include
 /Library/Developer/CommandLineTools/usr/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)

If you install ccls on macOS via HomeBrew, it will also install a separate clang/llvm package on your system. The llvm directory is like /usr/local/Cellar/llvm/11.0.0. If that is the case, the following .ccls file is also working for macOS:

clang
-ccc-install-dir
/usr/local/Cellar/llvm/11.0.0/bin
%cpp -std=c++11
%cpp -stdlib=libc++

It uses clang installed via homebrew. The standard C++ header file location now becomes /usr/local/Cellar/llvm/11.0.0/include/c++/v1/, also see here.

If your project is a CMake project, you can also generate a compile_commands.json file for ccls to work. For the details, see here.

Ref:

Syntax highlighting
#

If you do not want to use lsp-based highlight, you may try chromatica.nvim or vim-cpp-enhanced-highlight. Vim-cpp-enhanced highlight is base on regex matching to highlight symbols. It does not understand the code. It may not be accurate compared to LSP.

For lsp highlight, use vim-lsp-cxx-highlight. I can not make it work with nvim-lsp though. It works with vim-lsp.

Example config:

Plug 'jackguo380/vim-lsp-cxx-highlight'

tags and navigation
#

We can use gutentags to generate tags for us. Example config below:

let  g:gutentags_ctags_tagfile = '.tags'
let  s:vim_tags = expand('~/.cache/tags')
let  g:gutentags_cache_dir = s:vim_tags
let  g:gutentags_ctags_extra_args = ['--fields=+niazS', '--extra=+q']
let  g:gutentags_ctags_extra_args += ['--c++-kinds=+px']
let  g:gutentags_ctags_extra_args += ['--c-kinds=+px']

Compilation and run
#

If you are writing a large code project, you should use make or other build tools. For simple programs, using clang directly is convenient.

This is how to compile and run a simple program via the built-in terminal:

nnoremap <silent> <buffer> <F9> :call <SID>compile_run_cpp()<CR>

function! s:compile_run_cpp() abort
  let src_path = expand('%:p:~')
  let src_noext = expand('%:p:~:r')
  " The building flags
  let _flag = '-Wall -Wextra -std=c++11 -O2'

  if executable('clang++')
    let prog = 'clang++'
  elseif executable('g++')
    let prog = 'g++'
  else
    echoerr 'No compiler found!'
  endif
  call s:create_term_buf('v', 80)
  execute printf('term %s %s %s -o %s && %s', prog, _flag, src_path, src_noext, src_noext)
  startinsert
endfunction

function s:create_term_buf(_type, size) abort
  set splitbelow
  set splitright
  if a:_type ==# 'v'
    vnew
  else
    new
  endif
  execute 'resize ' . a:size
endfunction

References
#


  1. Strictly speaking, you can build ccls via clang, but I find it hard and can not seem to make it work. ↩︎

Related

Set up for C++/C Development in Neovim
··489 words·3 mins
A Complete Guide on Writing and Building C++ Programs in Sublime Text
··1114 words·6 mins
Pylsp setup for Neovim in 2023
··1020 words·5 mins