Table of Contents
- Compiling with Visual Studio
- Compiling with GCC
- When does ccls reindex a file?
- Full indexing takes a long time / crushes my system
- Can ccls be started directly?
- Does ccls support compile_flags.txt?
- Some C/C++ headers are not recognized
- Cross compiled projects
- Inspect index files
- Project root detection
- Maximum number of file descriptors
- Diagnostics in headers
- Multi-version headers
- Completion and diagnostics
- Definitions
- Hover
- References
- textDocument/implementation
- $ccls/vars
- $ccls/call
- $ccls/inheritance
- $ccls/member
- Reload
- Cases
- Be happy. Run Emacs as a native Windows binary, and compile ccls on Ubuntu-20.04 (WSL) with -D_WIN32 set in lsp.cc (top of source file). Put a ccls.bat script with one line in it: "powershell wsl ccls %*". Then add a /wsl$/Ubuntu-20.04 symlink back to /. Enjoy!
Compiling with Visual Studio
If your project is compiled with MSVC, you may set the compiler driver in
.ccls
to clang-cl
, and use the
initialization option
clang.extraArgs
to pass
three options: -fms-extensions -fms-compatibility -fdelayed-template-parsing
.
See https://clang.llvm.org/docs/MSVCCompatibility.html
Compiling with GCC
GCC is very similar to Clang and most options are portable. If you are using
options with GCC which are not available in Clang you can use
clang.excludeArgs
to
remove them.
If you want to convince Clang to use the GCC header files you can add the
--gcc-toolchain
option either via the .ccls
file
or the clang.extraArgs
initialization option. For example to choose the GCC system header files you
might add:
--gcc-toolchain=/usr
to your .ccls
file.
When does ccls reindex a file?
- The timestamp of the file, or any of its dependency has changed. See
pipeline.cc:indexer_Parse
. - The clang command line options have changed. See
pipeline.cc:cacheInvalid
.
Full indexing takes a long time / crushes my system
For large projects, full indexing can be a significant burden. Once the initial index is complete, future indexing will be proportional to the number of files changed.
While full indexing is required for complete cross-reference support, if you are making targeted edits on large codebases it may not be worthwhile to index the entire thing. There are various ways to reduce the impact and scope of indexing.
-
You can reduce the number of CPU cores used for indexing by setting the
index.threads
initialization option. -
You can avoid indexing parts of the codebase you are not interested in by setting the
index.initialBlacklist
initialization option. -
You can avoid indexing any of the codebase by default, and only indexing files as you open them, by setting
index.initialBlacklist
to["."]
(a regular expression that will match every file). -
You can copy
.ccls-cache
built from a more powerful machine. Seeclang.pathMappings
.
Note that when you edit a header file, ccls
will automatically open the
associated (by name) source file if one exists, and index it as well to locate
definitions of symbols declared in the header file. For example, if you edit
a file a.h
and ccls
can locate a file a.cpp
then it will index that as
well.
Can ccls
be started directly?
Yes ccls
can be run in stand-alone mode using the
--index
option. If your client uses
clang.extraArgs
be sure to use the same ones
when invoking ccls
in stand-alone mode, otherwise the flags mismatch will
cause re-indexing when invoked from the client.
Here's an example:
ccls --index=$HOME/llvm --init='{"clang":{"extraArgs": ["--gcc-toolchain=/usr"]}}'
Does ccls
support compile_flags.txt
?
No. https://github.com/MaskRay/ccls/issues/110
Some C/C++ headers are not recognized
Read on if you see bogus diagnostics like the following:
'stddef.h' file not found
'stdio.h' file not found
'iostream' file not found
use of undeclared identifier 'std'
ccls infers system search paths (e.g. /usr/include
, /usr/include/c++/9
). The underneath mechanism is provided by clang and the result should be similar to the output of clang -v -fsyntax-only -x c++ /dev/null
.
On Linux, if the clang
command does not print useful search paths, it usually indicates that gcc/g++ is not installed at well-known paths.
For example, if you have gcc-7
, g++-7
and gcc-8
installed (note the omission of g++-8
). clang may pick the gcc toolchain with the largest version number. The absence of g++-8
(actually libstdc++-8-dev
on Debian) will lead to the issue of unrecognized C++ headers.
A simple test
Create a temporary directory. Run git init; touch .ccls
.
Create a.cc
:
// a.cc
// system C header, usually in /usr/include
#include <stdio.h>
// system C++ header. The location varies among distributions, e.g. /usr/include/c++/{6,7.2.1}
#include <new>
// In Clang resource directory lib/clang/7.0.0, lib/clang/7.0.0/include/stddef.h
#include <stddef.h>
Open a.cc
in your editor, you should be able to jump to stdio.h
new
stddef.h
when you trigger textDocument/definition
on the include lines.
Note that this might not work on Windows. To solve this, add the system include directories to compile_commands.json via your build system of choice using the INCLUDE environment variable (available after executing VsDevCmd.bat).
For CMake this can be achieved in a single line: target_include_directories(<target> SYSTEM PRIVATE $ENV{INCLUDE})
Verify the Clang Resource Directory is correct
See Install#clang-resource-directory.
Cross compiled projects
Check if the search directories of
clang -v -fsyntax-only -x c++ /dev/null -target aarch64-linux-gnu
match
aarch64-linux-gnu-g++ -v -fsyntax-only -x c++ /dev/null
.
If not, add some missing directories via -isystem
. Example .ccls
file :
-target
aarch64-linux-gnu
-isystem/usr/aarch64-linux-gnu/include/c++/9
-isystem/usr/lib/gcc-cross/aarch64-linux-gnu/9/include
Inspect index files
The c-index-test
utility shipped with the clang package can give you insights what entities will be indexed:
c-index-test -index-file local /tmp/c/a.cc -isystem/usr/include/c++/7.3.0 -isystemyour_include_path2
(ccls indexes more entities but the underneath mechanisms are similar).
If you need to inspect the contents of the index files, use
cacheFormat
to specify JSON output format.
You can use jq
to format the JSON file to be more readable:
jq . < /tmp/ccls/@tmp@c/a.cc.json
Project root detection
emacs-ccls locates the project root in the following order:
.ccls-root
. If this file exists in any parent directory, that directory is treated as the project root.(lsp--suggest-project-root)
which in turn calls(projectile-project-root)
. Usually you don't want/usr/include/c++/8/algorithm
to be treated as in the project/usr/include/c++/8/
,(setq projectile-require-project-root t)
inhibits the behavior.
The root directory is sent to ccls (the language server) through the rootUri
field in the initialize
request.
ccls reads .ccls
or compile_commands.json
in the directory. If neither exists, ccls fallbacks to %clang $path
.
proj
.ccls-root # Use this file if you want subproject files to be associated with the root project
compile_commands.json
subproj0 # without .ccls-root, files will be associated with this root directory
.git
subproj1
.git
Maximum number of file descriptors
Some projects may require more than 1000 file descriptors. Remember to increase RLIMIT_NOFILE
.
ulimit -n 32768
/etc/security/limits.conf
:
* hard nofile 32768
* soft nofile 32768
Diagnostics in headers
For efficiency, headers are compiled on their own (as if clang [options] a.h
) to utilize Clang's preamble optimization.
For headers that are not self-contained (e.g. some /usr/include/bits/*.h
which require a predefined macro) ccls emits diagnostics that you don't see
when building the project.
// a.c
#define M
#include "a.h"
// a.h
#ifndef M
#error M not defined
#endif
One approach ccls explored before was to compile its import file a.c
, but it
caused severe performance issues for some projects. For smaller files this
does not matter and checking for a header guard may help the situation.
If missing declarations are known to be provided by base.h
, you can specify --include
,
e.g.
%compile_commands.json
%h %hpp --include=base.h
Multi-version headers
A header can be included in different translation units with different
compiler flags. It may behave differently via #ifdef
. Ideally, there should
be some mechanism to switch among different views (compiler flags) when you
are editing the file.
For efficiency, a header is only indexed with one set of compiler flags, so
either the #if
block or #else
is indexed, the other is left blank.
For macros, there is code to track different usage, thus you may find them
able to jump to multiple targets. index.multiVersion
is an experimental
option to index every version to certify that the author is aware of this
issue :) It may not be very useful though. It is of type number
but not
boolean
as the author is also aware of other dimensions of the multi-version
problem.
Completion and diagnostics
While most cross reference requests (textDocument/definition
textDocument/references
$ccls/member
...) are served by the index
(query.h:DB
) and have a global view, completion and diagnostics are
single-TU features. They use unsaved buffers and require re-parsing for each
new request.
Definitions
textDocument/definition
jumps to the definitions (there can be many across
many TUs, thinking of odr violation) if available. If the cursor is on a
definition, jump to declarations instead.
When jumping from a dependent name in a template, ccls tries its best to get
every target when the information is available. If the template is written in
a main file and you instantiate it multiple times, it can assuredly find all
target. However if the template is in a header, because by default a header is
indexed for one TU, you won't get all targets. See the discussion about
index.multiVersion
.
void foo();
A declaration jumps to the definitionvoid foo() {}
The definition lists all declarationsA a;
For variables of custom types, besides declarations of the variable, both the type and the variable jump to the declaration/definition of its typeA
class C {
jumps to declarations (and constructors/destructors)a.field
jumps to the member in the struct declaration#include <map>
jumps to the headerstd::string a = "a";
takes you to the constructor. Many implicit constructors can also be jumped in this way.a == b
operator==
for user defined operatorsnamespace ns {
find original or extension namespaces// ns::foo
in comments, it recognizes the identifier around the cursor, approximately finds the best matching symbol and jumps to it; onns
, it jumps to the namespace
Hover
textDocument/hover
is served by the index, which is built when you save the
file. This model is simple to implement and has lower CPU cycles, but the
drawback is that you don't get immediate hover information for newly-created
declarations that are not in the index.
See the initialization option index.onChange
.
References
#include <iostream>
lists all#include
lines in the project pointing to the included file[](){}
lists all(?) lambda expressions thanks to implicitstd::function
move constructorextern int a;
IfReferenceContext.includeDeclaration
is true, the definition and declarations are also listed.- If no references is found but the point is on the first line, list
#include
lines referencing current file.
textDocument/implementation
struct B{virtual void f();};
derived classes or virtual function overrides
$ccls/vars
A a;
lists all instances of user-definedA
.int i;
lists all instances ofint
.
$ccls/call
Find callers. If parameter callee:true
is specified, find callees instead.
Specify hierarchy:true
to enable hierarchical view.
$ccls/inheritance
Find base classes/overriden functions. If parameter derived:true
is
specified, find derived classes/functions instead. It also works fine for
jumping between primary template and partial specialization.
Specify hierarchy:true
to enable hierarchical view.
$ccls/member
Recursively list member variables of a record type.
struct A:B{void f()override;};
listsB
orB::f()
If parameter kind:3
is specified, list member functions/functions in a namespace
struct A{void f();};
listsA::f()
Sample requests:
// member variables / variables in namespaces: kind:0 (default)
{"jsonrpc":"2.0","id":1,"method":"$ccls/member","params":{"textDocument":{"uri":"file:///tmp/c/a.cc"},"position":{"line":0,"character":7}}}
// member functions / functions in namespaces: kind:3
{"jsonrpc":"2.0","id":2,"method":"$ccls/member","params":{"textDocument":{"uri":"file:///tmp/c/a.cc"},"position":{"line":0,"character":7},"kind":3}}
// nested classes / types in namespaces: kind:2
{"jsonrpc":"2.0","id":3,"method":"$ccls/member","params":{"textDocument":{"uri":"file:///tmp/c/a.cc"},"position":{"line":0,"character":7},"kind":2}}
Reload
- Notification
$ccls/reload
: reset global index and reload index files. This is useful if you have modified a common header file and you expect all opened files to be re-indexed. Note, this doesn't reload project files (compile_commands.json
and.ccls
). - Notification
workspace/didChangeConfiguration
: reload project files and re-index if necessary. - Restart the ccls process. Some language clients can restart the language server (e.g. lsp-mode
lsp-restart-workspace
).
Cases
On a laptop with one (i7-6600U CPU @ 2.60GHz, hyper-threading, nproc
=4), 4
indexers. llvm+clang+extra+lld+compiler-rt (2018-06-10)
- initial indexing: 1 hour; load: 20s
- resident set size is about 1GiB (the peak RSS during indexing is below 2GiB)
du -sh .ccls-cache
=> 580M
radare2 (2017-06-10)
CC=clang CXX=clang++ LDFLAGS=-fuse-ld=lld meson . debug --buildtype=debug --prefix ~/.config/radare2/debug
ln -s debug/compile_commands.json
- initial indexing: 78s; load: 3s
- 300MiB
du -sh .ccls-cache
=> 107M
Note: ccls can serve LSP requests while it is doing the initial indexing of the project.