Published on

Neovim C Cpp Lsp Integration Tips

Authors
  • avatar
    Name
    ttyS3
    Twitter

本文主要按 neovim lsp 来讲的,但是其中关于 lang server 的很多东西,其实是通用的。简单在此记录,备忘。

common lsp config

一般除了跳转定义,老灯还启用了 completion 自动完成以及 lsp-status 用于状态显示。

这里定义了一个公用的 mix_attach 用于lsp on_attach 事件

local lsp_status = require('lsp-status')
lsp_status.register_progress()
lsp_status.config({
    indicator_errors = "❌",
    indicator_warnings = "⚠️ ",
    indicator_info = "ℹ️ ",
    -- https://emojipedia.org/tips/
    indicator_hint = "💡",
    indicator_ok = "✅",
})

local cmp = require'completion'

--@param client: (required, vim.lsp.client)
local mix_attach = function(client)
  lsp_status.on_attach(client)
  cmp.on_attach(client)
end

neovim clangd lsp setup

clangd 的配置,主要参考 nvim-lspconfig 文档

注意这里对 handlers 的配置参考了 lsp-status 的示例文档

-- https://clangd.llvm.org/features.html
lsp.clangd.setup({
  handlers = lsp_status.extensions.clangd.setup(),
  init_options = {
    clangdFileStatus = true
  },
  on_attach = mix_attach,
  capabilities = lsp_status.capabilities
})

项目配置 -- 支持clangd

lsp 配置实际上比较简单,实际使用中,遇到问题的主要是关于如何让 lang server ( clangd 或 ccls ) 找到那些头文件。

To understand your source code, clangd needs to know your build flags. (This is just a fact of life in C++, source files are not self-contained).

By default, clangd will assume your code is built as clang some_file.cc, and you’ll probably get spurious errors about missing #included files, etc. There are a couple of ways to fix this.

主要原因在于,C 和 C++ 这俩语言比较古老,不像 Rust 或 Golang 那样自带包管理, 因此需要外部工具来帮助 lang server 理解代码。对于 clangd 来说,主要有两种解决办法:

1. compile_commands.json 法

This file provides compile commands for every source file in a project. It is usually generated by tools. Clangd will look in the parent directories of the files you edit looking for it.

虽然 clangd 的文档里说 clangd 会在你所编辑的文件的父目录中查找 compile_commands.json, 但实际使用中老灯发现能自动加载 build/compile_commands.json 文件,不知道是 neovim hack了还是 clangd 本身支持?

没时间细纠了,先这样吧。如果谁知道答案麻烦告知一下。

1.1 基于 CMake 的项目

这里又分两种情况,对于基于 CMake 的项目,只需要启用 CMAKE_EXPORT_COMPILE_COMMANDS 即可自动生成 compile_commands.json 文件。

启用 CMAKE_EXPORT_COMPILE_COMMANDS的方法主要有两种:

一是直接在命令行参数中指定,比如:

cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
# or
# cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=1

二是在 CMakeLists.txt 中添加:

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

1.2 基于其它构建系统的项目

对于其它构建系统 ( 主要是一些上古的 Makefile 类项目),要生成 compile_commands.json 需要用到一个叫 Bear 的工具。

Bear is a tool that generates a compilation database for clang tooling.

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.

One way to get a compilation database is to use cmake as the build tool. Passing -DCMAKE_EXPORT_COMPILE_COMMANDS=ON to cmake generates the compile_commands.json file into the current directory.

For non-cmake projects, Bear generates the JSON file during the build process.

Bear 的用法比较简单,直接在构建命令前面加bear 即可,如: bear make -j14

Bear 安装:

# fedora
sudo dnf install -y bear
# ubuntu
sudo apt-get install -y bear
# archlinux cn
sudo pacman -S bear
# archlinux aur
paru -S bear

除了 Bear, 还有其它工具也能生成 compile_commands.json

ninja build 也支持生成,如:

# Format: ninja -t compdb rule_names... > compile_commands.json
ninja -C out/Release -t compdb cxx cc > compile_commands.json

meson会自动生成:

meson build # generates compile_commands.json in the `build` directory

https://github.com/nickdiego/compiledb (基于python)

https://github.com/rizsotto/scan-build (python版,基于libear, uses Bear as a backend)

2. compile_flags.txt 法

If all files in a project use the same build flags, you can put those flags one-per-line in compile_flags.txt in your source root.

Clangd will assume the compile command is clang $FLAGS some_file.cc.

Creating this file by hand is a reasonable place to start if your project is quite simple.

compile_flags.txt 法主要是针对于项目中的所有文件都使用相同的 build flags 的情况。这个时候,你可以手撸一个 compile_flags.txt 来帮助 clangd 理解你的代码。

For simple projects, Clang tools also recognize a compile_flags.txt file. This should contain one argument per line. The same flags will be used to compile any file.

需要注意的是,This should contain one argument per line.

例如:

-xc++
-I
libwidget/include/

这里 -I libwidget/include 是两个参数,因此要各放一行 ( one argument per line )。

如果是相对路径,则该路径相对于 compile_flags.txt 文件所在目录

neovim ccls lsp setup

一般情况下使用clangd 即可了,没有必要配置两个lang server.

如果由于某些原因需要使用 ccls 的,老灯这里也说一下。

https://mesonbuild.com/配置部分比较简单,主要是参考 nvim-lspconfig 关于 ccls 的文档

-- https://github.com/neovim/nvim-lspconfig/blob/master/CONFIG.md#ccls
-- https://github.com/MaskRay/ccls/wiki
lsp.ccls.setup {
  init_options = {
	  compilationDatabaseDirectory = "build";
    index = {
      threads = 0;
    };
    clang = {
      excludeArgs = { "-frounding-math"} ;
    };
  }
}

项目配置 -- 支持ccls

根据ccls wiki:

ccls typically 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.

There are two main ways this happens:

  1. Provide compile_commands.json at the project root
  2. Provide a .ccls file. It is a line-based text file describing compiler flags. Recursively listed source files (headers excluded) will be indexed. Use an empty .ccls to get started (if you don't need specific -I, -D, etc).

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.

注意 ccls 只会在 project root 搜索 compile_commands.json文件。

If your compile_commands.json is not kept in the project root, set the initialization option compilationDatabaseDirectory to an alternative directory containing compile_commands.json.

因此前面我们的 neovim lsp 配置的时候,指定了 init_options.compilationDatabaseDirectory = "build"

第一种方法直接参考前面 clangd 部分。

第二种方法,参考ccls wiki: https://github.com/MaskRay/ccls/wiki/Project-Setup#ccls-file

Refs

https://github.com/neovim/nvim-lspconfig/blob/master/CONFIG.md#ccls

https://github.com/neovim/nvim-lspconfig/blob/master/CONFIG.md#clangd

https://github.com/nvim-lua/lsp-status.nvim#example-use

https://clangd.llvm.org/installation.html#project-setup

https://clangd.llvm.org/extensions.html#switch-between-sourceheader

https://github.com/MaskRay/ccls/wiki/Project-Setup

https://github.com/MaskRay/ccls/wiki

https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html

https://clang.llvm.org/docs/JSONCompilationDatabase.html

https://github.com/rizsotto/Bear

http://ninja-build.org/

https://mesonbuild.com/