Make Vim as your C/C++ IDE

This procedure can be very similar for
VSCode
andFleet
editors. In this case, you should only create thecompile_commands.json
file, and this procedure is illustrated in thebear
subsection, but I suggest you read the whole post.
It happened to me in the past that I had to configure Vim to manage C projects with some external library, and after lots of useless attempts, I gave up setting it up.
I am currently attending a university course called Advanced in Operating Systems. I am using a STM32 microcontroller and ST provides an “Eclipse 2010 based” IDE with lots of proprietary libraries, but as a Vim programmer writing even 10 lines of code on this IDE made me frustrated. So, I decided to try again, and as you can imagine if you are reading this blog post, I succeeded!
Requirements
-
Make sure use Vim >= 8.1.1719 or Neovim >= 0.4.0 and make
-
Install nodejs >= 14.14:
curl -sL install-node.vercel.app/lts | bash
Configuration
coc
Firstly, you should have a coc.nvim
plug-in installed. I am currently using vim-plug as
plug-in manager, but you can use any other.
In my case, I added it to my .vimrc
file
Plug 'https://github.com/neoclide/coc.nvim'
and run :PlugInstall
After that restart Vimand make sure you have
installed two important extensions, which are coc-json
and
coc-tsserver
using :CocList extensions
, if not
run :CocInstall coc-json coc-tsserver
coc-clangd
As you can imagine, coc-clangd
is an extension of coc.nvim
like the previous two, and you
can install it just by running :CocInstall coc-clangd
,
after that coc-clangd
will try to find clangd
from your $PATH
, if not found, you can run
:CocCommand clangd.install
to install the latest release
from GitHub.
At this point, you should be able to create
~/.vim/coc-settings.json
file and add the code below to
it
{
"languageserver": {
"ccls": {
"command": "ccls",
"args": ["--log-file=/tmp/ccls.log", "-v=1"],
"filetypes": ["c", "cc", "cpp", "c++", "objc", "objcpp"],
"rootPatterns": [".ccls", "compile_commands.json"],
"initializationOptions": {
"cache": {
"directory": "/tmp/ccls"
},
"client": {
"snippetSupport": true
}
}
}
},
"clangd.path": "~/.config/coc/extensions/coc-clangd-data/install/15.0.6/clangd_15.0.6/bin/clangd",
}
Obviusly, you should change the value of the “clangd.path” field with
your version, it may be something like
"clangd.path":
"~/.config/coc/extensions/coc-clangd-data/install/<version>/clangd_<version>/bin/clangd"
(note <version> on the string).
I do not want to bore you by explaining whole fields of the json
file, the only thing I would like to say to you is that this code is
used to specify the type of language server (ccls
in this
case) and its behavior.
ccls
Basically, ccls
, which
originates from cquery
, is a C/C++/Objective-C language
server.
It provides:
-
code completition (with both signature help and snippets)
-
definition/reference, and other cross references
-
diagnostics and code actions
-
semantic highlighting and preprocessor skipped regions
-
diagnostics and code actions
-
semantic highlighting and preprocessor skipped regions
and the other many usefull things.
You must install it because, as you can see in the
coc-settings.json
, the language server specified is
ccls
.
You can do it using your package manager, in my case
sudo apt-get install ccls
Setup
Typically, ccls
indexes an entire project. In order for
this to work properly, ccls
needs to be able to obtain the
source file list and their compilation command lines.
How ccls
obtains sources and compilation
commands
There are two main ways this happens:
-
Provide
compile_commands.json
at the project root -
Provide a .ccls file.
If neither exists, then when ccls
starts it will not
index anything: instead it will wait for LSP clients to open files and
index only those files.
I prefer use the first one, and to do that if you are using
Makefile
as build system for your C/C++ project you can
generate compile_commands.json
automatically with bear command, I will explain
in detail how in the next paragraph.
If you are not using Makefile
as build system, there are
lots of ways to generate this file, for example with CMake
,
compiledb
, scan-build
and so on. I suggest you
to take a look here.
bear
Bear is a tool that
generates a compilation database for clang tooling. The compilation
database is our compile_commands.json
, a simple json
file.
The JSON compilation database is used in the clang project to provide information on how a single compilation unit is processed. With this, it is easy to re-run the compilation with alternate programs.
Some build system natively supports the generation of JSON compilation database. For projects which does not use such build tool, Bear generates the JSON file during the build process.
You can install it using your package manager, in my case
sudo apt-get install bear
Use case
Consider this simple C project, but it can be extended to much larger projects.
I assure you that it works exactly as in the case I am going to illustrate now, the only important thing is to write your makefile correctly, but this is also obvious because otherwise your project would not compile. :D
.
├── inc/
│ └── main.h
├── src/
│ └── main.c
└── Makefile
// main.h
#define TEST "test"
// main.c
#include "main.h"
#include <stdio.h>
int main() {
int *ptrarray[4];
int w = 100, x = 200, y = 300, z = 400;
[0] = &w;
ptrarray[1] = &x;
ptrarray[2] = &y;
ptrarray[3] = &z;
ptrarray
for (int i = 0; i< 4; i++) {
("The value %d has the adddress %d\n", *ptrarray[i], ptrarray[i]);
printf}
("I am printing a variable of main.h file %d", &TEST);
printf
return 0;
}
It is a silly file that prints the values and memory area of the elements of an array.
CC = gcc
CFLAGS = -Wall
INC_DIR = inc
SRC_DIR = src
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
INC_FILES = $(wildcard $(INC_DIR)/*.h)
main: $(SRC_FILES) $(INC_FILES)
$(CC) $(CFLAGS) $(SRC_FILES) -o main -I$(INC_DIR)
But, the main problem is that Vim, specially
ccls
and coc
tells me that I have an error in
line 1 of the main.c
, and as you can imagine, the error is
main.h file not found
, and this problem is difficult to
manage manually, and it becomes even more difficult to manage if the
project uses external libraries.
This problem is also blocking all the LSP features, for example go-to-definition, go-to-implementation etc…
To solve it, we just have to run the
bear -- make
command, and it will generate the compile_commands.json
and there will be no more errors.
.
├── inc/
│ └── main.h
├── src/
│ └── main.c
├── compile_commands.json
├── main*
└── Makefile
and compile_commands.json
will be something like
this
[
{
"arguments": [
"/usr/bin/gcc",
"-c",
"-Wall",
"-Iinc",
"-o",
"main",
"src/main.c"
],
"directory": "/home/c_project",
"file": "/home/c_project/src/main.c",
"output": "/home/c_project/main"
}
]
For bigger projects, so for example the one I am currently working on for the Advanced in OS course that uses a lot of external libraries, this file can reach even 600 lines.