I want to share how I use Vim as C++ development environment, adding things like code completion, linting, formatting and snippet support. If you come from the IDE land and have been set your options with the checkbox on a GUI, you might need a mental shift to use text based configuration tools, like Vim.
There has been plugins to provide IDE-like features to Vim. Some of them were successful, some others evolved, yet others deprecated. I tried plugins like YouCompleteMe, they were great but the process that making them work wasn't a pleasant experience.
Creating a completion engine admittedly hard, porting those code to other editors without touching implementation details is virtually impossible.
The language server protocol developed solve this problem. The idea is, create a set of rule and implement a server and a client that follows the rules, then communicate over the protocol and provide functionalities like auto complete, go-to definition, search function calls, diagnostics.
Quoting from its website:
LSP creates the opportunity to reduce the m-times-n complexity problem of providing a high level of support for any programming language in any editor, IDE, or client endpoint to a simpler m-plus-n problem.
The server needs to installed on the host system, and client needs to provided by the editor, either as a plugin or build-in. In Vim, it must be a plugin, NeoVim has an experimental support. Some IDEs, like CLion, ship both out of package... with a price.
There are two actively developed language servers for C++: clangd and ccls. Both are great. I found clangd is somewhat easier to install. Precompiled binaries are available in here. APT packages also available at https://apt.llvm.org. On my fresh installed Debian 10, I just use:
apt-add-repository 'deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main'
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
apt-get update
apt-get install -qq clang-11 clang-tools-11 clang-11-doc libclang-common-11-dev \
libclang-11-dev libclang1-11 clang-format-11 clangd-11 clang-tidy-11 \
libc++-11-dev libc++abi-11-dev
... and that's it. Now along with the clangd, I have the latest stable C++ compiler, standard library implementation (that I can enjoy latest features added to the language), a linter and a code formatter. We got the server, what about the client?
To make use of clangd, we'll need a language client. Various implementations are available, I'll use coc.nvim. It's written in TypeScript needs Node.js runtime:
apt-get install npm
To add the coc.nvim and other plugins in easy way, a plugin manager required. Again, various options are available, I'll use vim-plug:
.vim/vimrc
:
call plug#begin('~/.vim/plugged')
Plug 'neoclide/coc.nvim', {'for':['zig','cmake','rust',
\'java','json', 'haskell', 'ts','sh', 'cs',
\'yaml', 'c', 'cpp', 'd', 'go',
\'python', 'dart', 'javascript', 'vim'], 'branch': 'release'}
call plug#end()
This is from my vimrc file. Plugins are listed between plug#begin
and
plug#end
as Plug 'Github_URI_path'
. It allows plugins work with certain filetype, which listed after
for
. I don't need and want this plugin to work other with filetypes, like .txt, .md etc.
:PlugInstall
will install and :PlugUpdate
update the plugins. A common
ritual among the vimmers, once plugin installed, reading its doc. Usually available with :help pluginName
.
coc.nvim holds server configurations in a JSON file called coc-settings.json
(with :CocConfig
). You can customize almost everything the way server behave.
Check out full scheme from
here. For
now, I'll just register language server and leave others default:
.vim/coc-settings.json
:
{
"languageserver":{
"clangd":{
"command":"clangd",
"filetypes":[
"c",
"cpp"
],
"rootPatterns":[
"compile_commands.json",
".git"
],
"args":[
"--compile-commands-dir=build",
"--compile_args_from=filesystem",
"--all-scopes-completion",
"--background-index",
"--clang-tidy",
"--cross-file-rename",
"--completion-parse=always",
"--completion-style=detailed",
"--function-arg-placeholders",
"--header-insertion-decorators",
"--query-driver=/usr/bin/**/clang-*,/home/adem/GCC-10/bin/g++*",
"--header-insertion=never",
"--limit-results=0",
"-j=6",
"--pch-storage=memory",
]
}
}
}
This calls clangd with the parameters listed on args array. Check out all options of clangd with clangd --help
.
You can add any number of server to languageserver
object. That way you can
have same editing experience, mapping, theme, etc for different languages on the same editor:
{
"languageserver":{
"clangd":{
// clangd options
},
"rls" {
// rls options
},
"bash-lsp" {
// bash-lsp options
}
}
}
For mappings (a term used for reassignable shortcuts in Vim), begin with the example configuration. If you want to take a look, this idiot holds his configurations on Github.
Now got the server and client, but need one more thing.
The last part of the ceremony involves using CMake. All we need is, adding a single line definition on toplevel CMakeLists.txt:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
This generates a file called compile_commands.json on build directory. It contains include paths, compiler command and options. These helps clangd to figure out what is where.
If the project don't use CMake, but make, you can use
Bear to generate this file with bear make
command.
The clang-tidy linter can be
called via clangd. A linter is a tool that shouts out the fishy parts of
the code. Make sure passed --clang-tidy
arg to clangd on coc-setting.json file.
You can enable some family of checks, and dump it into a file:
clang-tidy --checks='-*,bugprone-*' --dump-config > .clang-tidy
Clangd will detect this file and lint your code based on some criteria. See all
checks with: clang-tidy --list-checks
People used AStyle and some other tools to format their code. As a millennial, my first encounter with such a tool happen with clang-format. It's fairly easy to use:
clang-format -i source.cpp
vim-clang-format plugin allows us to use clang-format in automatized way, like formatting on every save:
.vim/vimrc
:
" between plug#begin/plug#end
Plug 'rhysd/vim-clang-format', {'for' : ['c', 'cpp']}
let g:clang_format#auto_format=1
Snippets are kind of underappreciated helpers. They reduce repetitive,
no-brainer jobs. Install coc.nvim snippet extension: :CocInstall coc-snippets
Let extension know where to find snippets:
.vim/coc-settings.json
:
"snippets.textmateSnippetsRoots": ["/home/adem/.config/snippets"],
On daily coding, when I type cmake
, it extends to:
cmake_minimum_required(VERSION |)
DESCRIPTION "|"
HOMEPAGE_URL https://github.com/p1v0t/ |
LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
where each |
waiting to be typed. Writing such a snippet is pretty easy. Just
like in VSCode, it's a JSON file:
/home/adem/.config/snippets/cmake.json
:
{
"cmake": {
"prefix": [
"cmake"
],
"body": [
"cmake_minimum_required(VERSION ${1:version})",
"",
"project(${2:projname}",
"DESCRIPTION \"${3:description}\"",
"HOMEPAGE_URL https://github.com/p1v0t/${4:reponame}",
"LANGUAGES CXX)",
"",
"set(CMAKE_EXPORT_COMPILE_COMMANDS ON)",
"${0}"
]
}
}
There is also an extension for CMake which provides basic completion. See list of extensions in here.
If you like to write small programs and don't need to build, SingleCompile plugin is just for this. Here is an example mapping:
nnoremap <F5> :SCCompileRunAF -g -Wall -Wextra -std=c++2a<cr>
When I press <F5>
, it compiles and run the executable. More on :h SingleCompile
This client-server architecture in mind, you can use similar setup on other platforms. In Windows, you can get Windows binaries of LLVM and use clangd on VSCode with clangd extension. On Emacs, there is a lsp mode support.
Feel free to embarrass me what I made with this post :) Thanks for reading.