- [Intro](#intro) * [Problem](#problem) * [Solution](#solution) - [Setup](#setup) * [A server](#a-server) * [A client](#a-client) * [compile_commands.json file generation](#compile-commandsjson-file-generation) - [Linting](#linting) - [Code formatting](#code-formatting) - [Snippets](#snippets) - [One shot compilations](#one-shot-compilations) ## 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](https://microsoft.github.io/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](https://langserver.org): > 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](https://github.com/neovim/nvim-lspconfig). Some IDEs, like CLion, ship both out of package... with a price. ### A server There are two actively developed language servers for C++: [clangd](https://clangd.llvm.org/) and [ccls](https://github.com/MaskRay/ccls). Both are great. I found clangd is somewhat easier to install. Precompiled binaries are available in [here](https://releases.llvm.org/download.html). APT packages also available at [https://apt.llvm.org](https://apt.llvm.org). On my fresh installed Debian 10, I just use: ```bash 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](https://langserver.org) implementations are available, I'll use [coc.nvim](https://github.com/neoclide/coc.nvim/). It's written in [TypeScript](https://www.typescriptlang.org) needs [Node.js](https://nodejs.org/en) 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](https://github.com/junegunn/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](https://github.com/neoclide/coc.nvim/blob/master/data/schema.json). 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](https://github.com/neoclide/coc.nvim/#example-vim-configuration). If you want to take a look, this idiot holds [his configurations](https://github.com/p1v0t/dotfiles) 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](https://cmake.org). 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](https://clang.llvm.org/docs/JSONCompilationDatabase.html) 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](https://github.com/rizsotto/Bear) to generate this file with `bear make` command. ## Linting The [clang-tidy](https://clang.llvm.org/extra/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](http://astyle.sourceforge.net) and some other tools to format their code. As a millennial, my first encounter with such a tool happen with [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It's fairly easy to use: ``` clang-format -i source.cpp ``` [vim-clang-format](https://github.com/rhysd/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`: ```shell "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](https://github.com/voldikss/coc-cmake) for CMake which provides basic completion. See list of extensions in [here](https://github.com/neoclide). ## One shot compilations If you like to write small programs and don't need to build, [SingleCompile](https://github.com/vim-scripts/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](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd). On Emacs, there is a [lsp mode](https://github.com/emacs-lsp/lsp-mode) support. Feel free to embarrass me what I made with this post :) Thanks for reading. ---