Skip to content

Instantly share code, notes, and snippets.

@dyigitpolat
Created January 26, 2021 22:05
Show Gist options
  • Save dyigitpolat/a3a82d88250075822de1a7e89183d0b9 to your computer and use it in GitHub Desktop.
Save dyigitpolat/a3a82d88250075822de1a7e89183d0b9 to your computer and use it in GitHub Desktop.
Using Vim for C++ development

Intro

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.

Problem

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.

Solution

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.

Setup

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.

A server

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?

A 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.

compile_commands.json file generation

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.

Linting

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

Code formatting

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

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.

One shot compilations

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.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment