lsp mode

chenli edited this page Jan 21, 2019 · 6 revisions
  1. Install lsp-mode and optionally, lsp-ui + company-lsp
  2. Install emacs-ccls (Melpa: and configure it
  3. Open a source file where either .ccls or compile_commands.json is in the project root (it works without them, if you don't need extra compiler command line options like -I)
  4. (require 'ccls), M-x lsp

ccls.el used to be based on the old interface lsp-mode.el of lsp-mode and the entry was lsp-ccls-enable.

(require 'lsp) and M-x lsp should be used instead now. See , the old interface lsp-mode.el is deperecated.

Alternatively, you may install



set c-c++-backend to lsp-ccls in the +lang/c-c++ layer


(use-package lsp-mode :commands lsp)
(use-package lsp-ui :commands lsp-ui-mode)
(use-package company-lsp :commands company-lsp)

(use-package ccls
  :hook ((c-mode c++-mode objc-mode) .
         (lambda () (require 'ccls) (lsp))))

The only required configuration is ccls-executable. Others have good defaults.

xref-find-definitions (default: M-.) / highlighting of the symbol at point / type signature in echo area should work out-of-box. Read on for customization and more features.

(setq ccls-executable "/path/to/ccls/Release/ccls")
;; ;; Some Linux distributions have packages that install to /usr/bin/ccls
;; (setq ccls-executable "/usr/bin/ccls")

;; ;; Log file
;; (setq ccls-args '("--log-file=/tmp/ccls.log"))

A more flexible way is to leave ccls-executable unchanged (default: ccls) and create a shell wrapper named ccls that is in your PATH:

#export CCLS_TRACEME=1 # if you want to debug ccls, stop it right after main() is called
#export LD_LIBRARY_PATH=~/llvm/Release/lib # if you link against shared library, not statically
exec ~/ccls/Release/ccls --log-file=/tmp/ccls.log "$@"

Some common settings to lsp-mode lsp-ui and emacs-ccls:

Projects with subprojects

When M-x lsp is invoked, projectile is consulted to locate the project root which determines the associated lsp-mode workspace. If your project has subprojects, (projectile-project-root) may think files in the subproject belong to the child workspace, which is not desired. touch .ccls-root in the root directory to override projectile roots.

Read lsp-project-whitelist lsp-project-blacklist if you don't want to start ccls for some paths.


If you are flycheck user, make sure (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc)), otherwise wrong error messages may occur.


  • (lsp--client-capabilities) LSP workspace is initialized correctly
  • M-: xref-backend-functions is (lsp--xref-backend) for cross references
  • M-: completion-at-point-functions is (lsp-completion-at-point) for completion

The buffer *lsp-ccls stderr* and --log-file=/tmp/cq.log contain logs.

Also refer to Debugging.

Initialization options

Initialization options are defined in config.hh, but you need to customize them in S-exp. Use t for true, :json-false for false, :json-null for null.

(setq ccls-initialization-options '(:index (:comments 2) :completion (:detailedLabel t)))

Find definitions/references

xref-find-definitions (default: M-.) should work out-of-box. If not, check if M-: xref-backend-functions is (lsp--xref-backend), which should be set by lsp-mode when you execute (lsp-ccls-enable).

There is heuristic to make textDocument/definition work in comments and macro replacement-list, which does something similar to rtags-find-symbol-at-point

xref-find-references (default: M-?) will give a prompt. To inhibit the prompt, add xref-find-references to xref-prompt-for-identifier. (See for changing the default behavior.)

There are Helm/Ivy alternatives for xref-find-{definitions,references,apropos}

lsp-ui-peek-find-{definitions,references} from lsp-ui-peek.el provide peek views. An additional benefit is you get a window local jump list dedicated to cross references (lsp-ui-peek-jump-forward lsp-ui-peek-jump-backward)


references + hydra

Refer to MaskRay's doom-emacs configuration for fine-grained textDocument/references (different roles: Read/Write/Dynamic/...)


The identifier (or user-defined operator) at point is highlighted with its other references. You may customize lsp-face-highlight-*. textDocument/documentHighlight

imenu/workspace symbol search

Use lsp-imenu.el for textDocument/documentSymbol (outline). lsp-ui-imenu.el provides a sidebar.

lsp-imenu + hydra

xref-find-apropos (default: C-M-.) invokes workspace/symbol to fuzzy-find symbols in the project. However, the function tries to be smart and splits the pattern and regexp-quote them for you, which actually gets in the way. Consider using lsp-ui{,-peek}-workspace-symbol in the lsp-ui package.

For a symbol named Name::FooBar, all of FooBar, foo bar, nafoba find them, but with different priorities.

Cross reference extensions

Aside from definitions/references/workspace symbol, ccls provides some LSP extensions that find base/derived classes/methods, vars of a type, callers of a function. You may call:

;; direct callers
(lsp-find-custom "$ccls/call")
;; callers up to 2 levels
(lsp-find-custom "$ccls/call" '(:levels 2))
;; direct callees
(lsp-find-custom "$ccls/call" '(:callee t))

(lsp-find-custom "$ccls/vars")
; Use lsp-goto-implementation or lsp-ui-peek-find-implementation (textDocument/implementation) for derived types/functions
; $ccls/inheritance is more general

;; Alternatively, use lsp-ui-peek interface
(lsp-ui-peek-find-custom "$ccls/call")
(lsp-ui-peek-find-custom "$ccls/call" '(:callee t))

Recommended helpers:

(defun ccls/callee () (interactive) (lsp-ui-peek-find-custom "$ccls/call" '(:callee t)))
(defun ccls/caller () (interactive) (lsp-ui-peek-find-custom "$ccls/call"))
(defun ccls/vars (kind) (lsp-ui-peek-find-custom "$ccls/vars" `(:kind ,kind)))
(defun ccls/base (levels) (lsp-ui-peek-find-custom "$ccls/inheritance" `(:levels ,levels)))
(defun ccls/derived (levels) (lsp-ui-peek-find-custom "$ccls/inheritance" `(:levels ,levels :derived t)))
(defun ccls/member (kind) (interactive) (lsp-ui-peek-find-custom "$ccls/member" `(:kind ,kind)))

;; References w/ Role::Role
(defun ccls/references-read () (interactive)
  (lsp-ui-peek-find-custom "textDocument/references"
    (plist-put (lsp--text-document-position-params) :role 8)))

;; References w/ Role::Write
(defun ccls/references-write ()
  (lsp-ui-peek-find-custom "textDocument/references"
   (plist-put (lsp--text-document-position-params) :role 16)))

;; References w/ Role::Dynamic bit (macro expansions)
(defun ccls/references-macro () (interactive)
  (lsp-ui-peek-find-custom "textDocument/references"
   (plist-put (lsp--text-document-position-params) :role 64)))

;; References w/o Role::Call bit (e.g. where functions are taken addresses)
(defun ccls/references-not-call () (interactive)
  (lsp-ui-peek-find-custom "textDocument/references"
   (plist-put (lsp--text-document-position-params) :excludeRole 32)))

;; ccls/vars ccls/base ccls/derived ccls/members have a parameter while others are interactive.
;; (ccls/base 1) direct bases
;; (ccls/derived 1) direct derived
;; (ccls/member 2) => 2 (Type) => nested classes / types in a namespace
;; (ccls/member 3) => 3 (Func) => member functions / functions in a namespace
;; (ccls/member 0) => member variables / variables in a namespace
;; (ccls/vars 1) => field
;; (ccls/vars 2) => local variable
;; (ccls/vars 3) => field or local variable. 3 = 1 | 2
;; (ccls/vars 4) => parameter

;; References whose filenames are under this project
(lsp-ui-peek-find-references nil (list :folders (vector (projectile-project-root))))

Hierarchies provide a flattened xref interface:

Documentation (comments)

lsp-ui-doc.el renders comments in a child frame (Emacs >= 26) or inline (< 26).

    (setq lsp-ui-doc-include-signature nil)  ; don't include type signature in the child frame
    (setq lsp-ui-sideline-show-symbol nil)  ; don't show symbol on the right of info


Add company-lsp to company-backends. ccls has a fuzzy matching algorithm to order candidates according to your query. You may want to disable client-side cache and sorting:

(setq company-transformers nil company-lsp-async t company-lsp-cache-candidates nil)

company-lsp + company-quickhelp


Semantic highlighting

To enable semantic highlighting:

(setq ccls-sem-highlight-method 'font-lock)
;; alternatively, (setq ccls-sem-highlight-method 'overlay)

;; For rainbow semantic highlighting

Different variables/functions/types will be assigned different faces, while uses of the same variable/function/type share the same face. The colors can be customized:

;; ...
ccls-sem-member-face  ;; defaults to t :slant italic

;; To customize faces used
(face-spec-set 'ccls-sem-member-face
               '((t :slant "normal"))

by default only one face is used for each symbol kind (type/function/variable/member function)

While (setq ccls-sem-highlight-method 'overlay) is more accurate than 'font-lock, it may cause severe performance issues. The short story is that the large number of overlays generated by ccls creates a similarly large number of markers. If the buffer currently edited is a multibyte buffer and contains at least one non-ASCII character, these markers may slow down line-number-at-pos (a function heavily relied upon by lsp-mode) by orders of magnitude; the effect gets worse with increasing distance from (point-min). For the long story, refer to the corresponding emacs-devel thread; if you wish to use ccls-sem-highlight without the associated slowdown, the following options exist (should you find more, please add them to this wiki page):

  1. while this patch by Stefan Monnier does not completely solve the underlying performance issue, it significantly alleviates it for usual buffer sizes

  2. the noverlay branch of Emacs reimplements overlays in a way that avoids markers. Although large numbers of markers would still lead to performance issues with the noverlay branch, ccls performance should be excellent

  3. both 1. and 2. require recompiling Emacs from sources; if that is unacceptable, performance can be restored using (set-buffer-multibyte nil). Note, however, that this will lead to non-ASCII characters being displayed as escape sequences. Also, this workaround has been verified to remove the performance penalty affecting line-number-at-pos, but it has not been tested whether disabling multibyte handling causes other issues with lsp-mode or ccls.

Call/member/inheritance Hierarchies || primary template/partial specialization

M-x ccls-member-hierarchy $ccls/member hierarchy:true

(ccls-call-hierarchy nil) ; caller hierarchy
(ccls-call-hierarchy t) ; callee hierarchy


(ccls-inheritance-hierarchy nil) ; base hierarchy
(ccls-inheritance-hierarchy t) ; derived hierarchy

$ccls/inheritance hierarchy:true Side notes: ccls-inheritance-hierarchy also works fine with jumping between primary template and partial specialization.

Code lens

M-x ccls-code-lens-mode

You may customize ccls-code-lens-position. It is 'end by default, which puts code lenses at line ends (implemented with overlay's 'display property). There is an issue that opening new lines may drag the lens to the next line.


Think of them as sp-{down,previous,next,down}-sexp for C/C++, roughly semantic movement among declarations.

(ccls-navigate "D") ;; roughly sp-down-sexp
(ccls-navigate "L")
(ccls-navigate "R")
(ccls-navigate "U")


For out-of-band changes to the files in the workspace that are not made in the LSP client (e.g. git pull), call (ccls-reload) to reload/rebuild indexes for every file.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.