ccls從cquery fork出來,有一些index上的小改進和pipeline上的大改動,以及很多地方細微的改進:
-
$ccls/memberHierarchy
(M-x ccls-member-hierarchy
,非常有用(說過只用definition references的人聽😁),我把這個綁定到x m
)顯示field offset -
workspace/symbol
排序結果改進,ccls加了一個qual_name_offset
。我經常用空格分割查詢關鍵詞搜索,比ripgrep精確一些 - Arch Linux不寫
-isystem /usr/include
會導致系統標頭檔無法被索引。這裏路徑中有多餘..
,需要用一個Clang C++ API tryGetRealPathName,我很久前加到libclang了,但clang>6才有,ccls #ifdef 修了。 - pipeline我覺得是巨大改進。之前
import_pipeline.cc
和各個*_manager.cc
實現超級複雜,在標頭檔修改保存能觀察到引用丟失或重複。IndexMerge等都是沒必要的設計 - index上
uint64_t usr
作爲符號主鍵,但cquery還用了一個uint32_t id,其實並無必要。這個倒是有歷史原因,起先symbol主鍵用std::string的,後來發現clangd用了SHA1,我覺得對於cquery的本地使用場景,uint64_t足夠(uint32_t不夠;因爲對於linux llvm等專案,符號數幾十萬接近百萬,birthday problem square approximation容易故障)
libclang是大約2010年由Apple創立的專案,用於Xcode,提供面向clang的穩定的C接口,提供indexing completion等功能。經過多年演化,被越來越多專案採用,比如irony rtags KDevelop cquery …
- rtags主要用自製的AST traversal,用
clang_getCursorReferenced
獲得引用關係,clang_getCursorSpelling
等獲得符號信息。libclang無法訪問完整的Clang C++ AST,很多地方不方便,因此用了很多heuristic,src/ClangIndexer.cpp
(實作有點亂難以理清), 比如模擬macro replacement-list的單次展開,獲得replacement-list中的引用信息。符號存儲的信息很多src/Symbol.h
,存儲設計得不好,~/.cache/rdm
很浪費,大約是ccls 10倍 - cquery主要用
clang_indexTranslationUnit
,這是套在clangIndex上的高層API,提供callbacks用於處理#include
(你在#include
上textDocument/definition
可以跳轉) declaration reference等。我給clang_indexer.cc
改過很多,比如:- template body沒有被訪問,我只好寫了個簡易traversal(這個和rtags方式有點像)
- lambda parameter, template parameter不會被declaration callback調用
- macro replacement-list裏引用無法處理。這個無解。但我加過一個heuristic,類似rtags-find-symbol-at-point,因爲有全局符號信息,查找和point處識別字最接近的即可
- 一些dependent name沒有引用信息
- irony只用了libclang的completion
- ycmd/cpp/ycm也用了completion和clang_getCursorReferenced clang_getCursorType等。沒有全局符號信息,因此無法查找引用 我剛剛粗略看了下,覺得程式碼不簡單因爲封裝libclang API太多了,依賴也多。
週四和週末把indexer部分用clangIndex改寫了。indexer.cc
少了1000多行,其他clang_tu
也刪除了百行。我看submodule doctest不舒服也刪了……這樣下來ccls 11693而cquery 19151
改寫成clangIndex後獲得了增強xref的可能性。比如下面例子,macro replacement-list引用:
a.cc
#include "a.h"
void bar(A a) {
a.X;
a.foo;
}
a.h
struct A { int foo; };
#define X foo // 換成clangIndex後,如果你用clang>6,foo這個地方會是A::foo的一個引用
通過IndexImplicitInstantiation
獲得檢索下面例子的能力(可以知道an occurrence of B::bar in A::foo)
template <class T> struct B { void bar() {}};
template <class T> struct A { void foo(B<T> *x) { x->bar(); }};
int main() { A<int> a; a.foo(0); }
更複雜的例子不見得可行,因爲多執行續、增量更新環境索引更新是個困難的問題。
另外優化了一下emacs-ccls的$ccls/publishSemanticHighlighting
。
emacs-ccls是lsp-mode的adapter,用於C/C++/ObjC (ObjC幾乎沒測試過) major-mode。
ccls--initialize-client
用lsp-client-on-notification
向lsp-mode註冊ccls server實現的LSP非標準notification方法。
這是LSP中一個區間的定義:
interface Position {
line: number;
character: number;
}
interface Range {
start: Position;
end: Position;
}
一個Range會被JSON serialize成這樣:"range":{"start":{"line":5,"character":4},"end":{"line":5,"character":5}
,相當浪費。而且emacs中forward-line
效率不高,如果對於每個range都(goto-char 1)
再(forward-line line)
會慢得難以接受。我後來加了一個優化。把區間按行排序後從往下遞增(forward-line ..)
,有的時候會碰到End of buffer
問題,不知道如何解決。剛剛順便把這裏優化了,用point代替行列標識的Position,現在一個區間表示成{"L":3869,"R":3873}
(其實用兩個元素的Array也行,但不太直觀)。現在感覺快多了。對於lmdb mdb.c
(11000+行巨大檔案),這樣優化後依然要傳輸500+KB,但感覺變快了。