Header Context
为了让 clangd 正常工作,用户往往需要提供一份compile_commands.json
文件(后文简称 CDB 文件)。C++ 的传统编译模型的基本编译单元是一个源文件(例如.c
和.cpp
文件),#include
只是把头文件的内容粘贴复制到源文件中对应的位置。而上述 CDB 文件里面就储存了各个源文件对应编译命令,当你打开一个源文件的时候,clangd 会使用其在 CDB 中对应的编译命令来编译这个文件。
那很自然的就有一个疑问,如果 CDB 文件里面只有源文件的编译命令,没有头文件的,那么 clangd 是如何处理头文件的呢?clangd 会把头文件当做一个源文件进行处理,然后呢,根据一些规则,比如使用对应目录下的源文件的编译命令作为该头文件的编译命令。这样的模型简单有效,但是却忽略了一些情况。
由于头文件是源文件的一部分,那么就会出现它的内容根据它在源文件中前面的内容不同而不同的情况。例如:
// a.h
#ifdef TEST
struct X { ... };
#else
struct Y { ... };
#endif
// b.cpp
#define TEST
#include "a.h"
// c.cpp
#include "a.h"
显然,a.h
在b.cpp
和c.cpp
中具有不同的状态,一个定义了X
,一个定义了Y
,如果简单的把a.h
当做一个源文件进行处理,那么就只能看得到Y
。
一个更极端的情况是 non-self-contained 头文件,例如:
// a.h
struct Y {
X x;
};
// b.cpp
struct X {};
#include "a.h"
a.h
自身不能被编译,但是嵌入到b.cpp
中的时候就编译正常了。这种情况下 clangd 会在a.h
中报错,找不到X
的定义。显然这是因为它把a.h
当成一个独立的源文件了。在 libstdc++ 中的代码中就有很多这样的头文件,现在流行的一些 C++ 的 header-only 的库也有些有这样的代码,clangd 目前无法处理它们。
clice 将会支持头文件上下文 (header context),支持自动和用户主动切换头文件的状态,当然也会支持非自包含的头文件。我们想要实现如下的效果,以最开始那份代码为例。当你从b.cpp
跳转到a.h
的时候使用b.cpp
作为a.h
的上下文。同理,当你从c.cpp
跳转到a.h
的时候则使用c.cpp
作为a.h
的上下文。