libparser/C.c
。用Berkeley DB存储definition/reference/path
name。带有插件系统可以使用ctags
idutils的parser。对于Emacs/Vim用户来说,可能是tag流派中最好用的工具了。辅以一些heuristics和
ripgrep
等,很多用户不觉得自己生活在水深火热中……
clang流派
std::vector
(cquery风格)足够应对大部分使用场景。很担心他们走上歧路。
ReferencesProvider,HoverProvider,DefinitionProvider
,且交互使用可能有极大延迟。大多数人并不在意C++
Haskell Python代码间无缝跳转。
https://github.com/google/kythe/tree/master/kythe/cxx/indexer/cxx
info,symbols,symnames,targets,tokens,usrs
(过多),没有使用in-memory索引,查找引用请求会读项目所有translation
units的文件。导致性能低下
https://github.com/Andersbakken/rtags/issues/1007
。rtags.el里应该还有很多东西可供Emacs
lsp-mode学习,有经验的人介绍一下~
std::vector
(
src/indexer.h
)。有一些Emacs用户积极贡献
code
navigation功能
。
IDE(Any sufficiently complicated IDE contains an ad-hoc, informally-specified, bug-ridden, slow implementation of half of C++.)
/usr/bin/cquery
)
./waf configure
# 或用--bundled-clang=5.0.1选择
http://releases.llvm.org/
上的release版本
./waf build
#
构建
build/release/bin/cquery
compile_commands.json
,参见下文。
cquery是一个C++ language server,它和编辑器端的LSP client协作流程如下:
当编辑器打开C++文件时,language client插件启动language server进程(根据配置的language server可执行文件),用 JSON-RPC 2.0 协议通过stdio通信,协议规范见 https://microsoft.github.io/language-server-protocol/specification 。
Language client插件用
initialize
请求告知language
server(这里是
build/release/bin/cquery
进程)自己支持的功能(ClientCapabilities)、项目路径(rootUri)、初始化选项(initializationOptions,cquery需要知道
cacheDirectory
路径)。之后各种语言相关功能都通过与language
server通信实现:
textDocument/hover
请求,language
server返回变量/函数声明信息、注释等。VSCode使用浮动窗口显示,Emacs
lsp-mode用eldoc显示
textDocument/definition
请求,language
server返回定义所在的文件、行列号。编辑器的可能行为:单个结果时直接跳转到目标文件的指定行列,如有多个候选则用菜单显示
textDocument/references
请求,和查找定义类似
textDocument/documentSymbol
请求,language
server返回符号和行列号
workspace/symbol
请求
textDocument/completion
,language
server提供候选及排序方式,是否使用snippet,如何编辑文档得到补全结果等
textDocument/didChange
,language
server据此更新自己的模型
$cquery/derived
用于查找派生的类、方法等
参照 https://github.com/jacobdufault/cquery/wiki/Emacs 配置。需要安装几个插件:
emacs/cquery.el
。地位与lsp-rust、lsp-haskell等类似,把cquery适配到lsp-mode。另外支持cquery一些Language
Server Protocol之外的扩展。
textDocument/hover
信息
find-{definitions,references,apropos}
textDocument/completion
信息提供补全
这些插件只有lsp-mode和cquery.el是必须的。
(lsp-enable-imenu)
开启,用imenu来显示
textDocument/documentSymbol
信息。跳转到当前档案的符号很方便。
textDocument/hover
,显示类型、变量、函数等的fully
qualified name,有层层namespace嵌套时容易定位。对于auto
specifier,能知道具体类型。
M-x lsp-format-buffer
发送
textDocument/formatting
。参见
cquery/wiki/Formatting
xref.el是Emacs自带的。lsp-mode设置
xref-backend-functions
,让xref.el使用lsp后端。如果不安装其他库,也能用以下三个函数,结果由xref.el渲染。
xref-find-definitions
(默认
M-.
),查找定义,发送
textDocument/definition
请求
xref-find-references
(默认
M-?
),查找引用,发送
textDocument/references
请求
xref-find-apropos
(默认
C-M-.
),查找项目符号,发送
workspace/symbol
请求
xref-find-definitions
若只有一个结果会直接跳转,有多个则弹出菜单供用户选择。而
xref-find-references
会触发
xref--read-identifier
,在minibuffer中要求读入一个串。这显然和期望的查找当前光标位置引用的使用方式不符。另外,lsp-mode会读取光标处标识符的text
properties信息(其中编码了buffer内位置信息),而prompt读入的串是不带text
properties的。
xref-find-references
会失败。
要让它工作,请阅读
xref-prompt-for-identifier
文档,把
xref-find-references
添加进
xref-prompt-for-identifier
。我提交了一个bug到Emacs(因为
xref.el
是Emacs一部分):
https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29619
,但maintainer需要了解更多用户的反馈才会修改
xref-prompt-for-identifier
默认值。
cquery
workspace/symbol
使用了一个sequence
alignment结合词结合性、camelCase等启发因素的fuzzy
matching算法,以
foo bar
为模式会返回
fooBar foobar foozbar
等,
fooBar
排在前面。
xref-find-apropos
会自作聪明地把模式用空格分割后当作正规表达式转义,考虑自定义。
提供LSP的补全支持。我用spacemacs的
(spacemacs|add-company-backends :backends company-lsp :modes c-mode-common)
。
tumashu写了一个company-childframe.el,可能需要人推动一下 company-mode#745 。
cquery.el
cquery项目中的
cquery.el
适配cquery到lsp-mode,同时提供一些LSP协议未定义的功能。如inactive
region,把preprocessor忽略掉的行用灰色显示:
我用以下C/C++ mode
hook在项目根目录有
compile_commands.json
时自动启用`lsp-cquery-enable。
1 |
(defun my//enable-cquery-if-compile-commands-json () |
另外一些不在LSP协议中的cquery扩展方法,如:
$cquery/base
用于类型是查找base class,也可用于virtual
function
$cquery/derived
用于类型是查找derived
classes,也可用于virtual function查找被哪些derived classes override
$cquery/vars
查找一个类型的所有变量
另外有个
$cquery/typeHierarchyTree
,但还没有人搬到Emacs,用空的话用个画树的库造福其他人~
helm
用户可以考虑安装
helm-xref
,
(setq xref-show-xrefs-function 'helm-xref-show-xrefs)
即可。
xref-find-{definitions,references,apropos}
会用helm显示,替代
xref.el
的界面。
helm-xref效果如图
。
使用了child frame,需要Emacs 26或以上。
Language
client中命令行设置为
cquery --language-server --enable-comments
可以索引项目中的注释(文档)。
textDocument/hover
信息除了提供类型签名,还会提供注释。
1 |
(setq lsp-ui-doc-include-signature nil) ; don't include type signature in the child frame |
1 |
(with-eval-after-load 'lsp-mode |
lsp-ui 提供了不同于xref.el的另一套交叉引用。参见其主页的demo。
1 |
M-x lsp-ui-peek-find-definitions |
以下三个cquery扩展协议也很有用,建议设置快捷键。
1 |
(lsp-ui-peek-find-custom nil "$cquery/base") |
1 |
(setq lsp-ui-sideline-show-symbol nil) ; don't show symbol on the right of info |
注意
textDocument/references
协议
中定义返回结果为
Location[] | null
,只含位置信息,不包含代码行内容。显示行内容是lsp-mode做的。
我的配置: https://github.com/MaskRay/Config/blob/master/home/.emacs.d/layers/%2Bmy/my-code
reference-handler
(类似于跳转到定义的
jump-handler
)也很有用:https://github.com/syl20bnr/spacemacs/pull/9911
(setq-local eldoc-documentation-function ...)
,对于这类minor-mode冲突问题,如果能设置优先级就能优雅解决。
参照 https://github.com/autozimu/LanguageClient-neovim/wiki/cquery 。相关组件:
cquery.el
的对应物,提供LSP协议未定义的cquery特定功能
1 |
nn <leader>ji :Denite documentSymbol<cr> |
cquery这类Clang LibTooling工具和传统tag-based工具的一大差别是了解项目中每个源文件和编译方式。放在项目根目录的 compile_commands.json 提供了这种信息。
1 |
% mkdir build |
Bear
is a tool that
generates a compilation database for clang tooling. It can be used for
any project based on
Makefile
.
1 |
bear make |
1 |
ninja -t compdb rule_names... > compile_commands.json |
cquery使用Clang的C接口libclang parse/index文件。Clang C++
API不稳定,cquery使用C++
API可能会难以适配不同Clang版本。使用
--use-clang-cxx
编译选项可以用Clang
C++ API。但注意可能会显著增加cquery构建时间。Windows
releases.llvm.org的bundled clang+llvm不带C++头文件。
--enable-comments
可以索引项目中的注释,VSCode渲染完美,但Emacs
Vim
的显示还在改善中。注释的排版,如何parse
comment markers(
Doxygen
,
standardese
)还有
很多争论
。
#include <algorithm>
在include行也能跳转,但如果是项目外的文件(系统头文件),你的LSP
client可能不会把它和之前的LSP
session关联,你就无法在新打开的buffer中用LSP功能了。
a
上
textDocument/definition
会跳到类型
A
的定义。在
a
旁边的空格或分号会跳到constructor。因为constructor标记为implicit,代码中让implicit函数调用的范围左右扩展一格,那么就更容易触发了。
A a(3);
在
(
或
);
上
textDocument/definition
会跳到类型constructor。
A a=f();
如果有隐式copy/move
constructor,在
(
上能跳到它们。
assert(1);
在
assert
上会跳到
#define assert
,但在
(1)
上会跳到
__assert_fail
,
__assert_fail
来自
assert
macro的展开。libclang
IndexerCallbacks.indexEntityReference
回调会报告来自
__assert_fail
的引用,因此请不要惊讶。
auto a = std::make_unique<A>(3);
make_unique
会跳转到constructor,因为
src/indexer.cc
中对
make
开头的模板函数有特殊逻辑,会跳到constructor而不是
make_unique
的定义。
function/class
template里有些东西有def/ref信息,但
A<int>::foo()
等引用跳转不了,是因为模板索引的困难
#174
。
有余力~请更新 https://github.com/jacobdufault/cquery/wiki ~
SQLITE_ENABLE_LOCKING_STYLE
、flock很难。
索引Linux kernel
1 |
wget 'https://git.archlinux.org/svntogit/packages.git/plain/trunk/config?h=packages/linux' -O .config |
生成3GiB文件。
索引llvm,
du -sh => 1.1GB
,索引完内存占用2G。
查看LSP requests/responses
1 |
sudo sysdig -As999 --unbuffered -p '%evt.type %evt.buffer' "proc.pid=$(pgrep -fn build/app) and fd.type=pipe" | egrep -v '^Content|^$' |
希望有朝一日Debug Protocol也能获得重视, https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts ,让 realgud 轻松一点。
和YouCompleteMe等项目一样,cquery默认下载
prebuilt
clang+llvm
,即
.h .so .a
。用户不需要编译完整的llvm,开发门槛比clangd低。
哪些源文件处理不好:
感谢ngkaho1234。