把ccls的indexer用clangIndex改寫了

cclscquery 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(你在#includetextDocument/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-clientlsp-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,但感覺變快了。

3 个赞

哦,原来ccls的作者在这呢,那下次发issue到github的时候,就可以直接用中文了

刚重新安装了AUR/ccls-git,崩了。具体我发到ccls的github issue了,麻烦你有时间看一下。

另外,你现在完全转到doom-emacs了嘛?

創建了一個gitter https://gitter.im/ccls-project/ccls

aur/ccls-git 確實有問題。需要帶符號信息的extra/clang才能查錯

macro replacement-list裏的東西可以檢索了

1 个赞