Skip to content
Tianyi Li edited this page Apr 19, 2022 · 12 revisions
  1. Install lsp-mode and optionally, lsp-ui + company-lsp (company-lsp was deprecated and no longer works with lsp-mode, reference https://github.com/scalameta/metals/issues/2641. You only can install it manually)
  2. Install emacs-ccls (Melpa: https://melpa.org/#/ccls) 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

Configure

doom-emacs

Set (cc +lsp) in your ~/.config/doom/init.el

spacemacs

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

use-package

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

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

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

(setq ccls-executable "/path/to/ccls/Release/ccls")
;; (setq ccls-args '("--log-file=/tmp/ccls.log"))

You may leave ccls-executable unchanged (default: ccls) and create a shell script wrapper.

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.

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.

flycheck

flymake is used by default.

To use flycheck, (setq lsp-prefer-flymake nil). You may also need (setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-cppcheck c/c++-gcc)) to disable other checkers.

Diagnostics

  • (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.

You need to customize initialization options 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 https://debbugs.gnu.org/cgi/bugreport.cgi?bug=29619 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)

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

documentHighlight

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

There are two ways to invoke textDocument/documentSymbol (outline):

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

See LSP Extensions for the description of these extensions.

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 ()
  (interactive)
  (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

Completion

DO NOT install company-lsp by melpa. This package is deprecated and no longer works with lsp-mode, from https://github.com/scalameta/metals/issues/2641. You can install it manually.

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)

Install company-quickhelp to see the attached comments for the selected completion item.

If completion does not work for you, make sure company-lsp is used: run M-x company-diag and make sureUsed backend: includes company-lsp.

Semantic highlighting

To enable semantic highlighting:

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

;; For rainbow semantic highlighting
(ccls-use-default-rainbow-sem-highlight)

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-function-colors
ccls-sem-macro-colors
;; ...
ccls-sem-member-face  ;; defaults to t :slant italic

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

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-call-hierarchy nil) ; caller hierarchy
(ccls-call-hierarchy t) ; callee hierarchy

$ccls/call

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

$ccls/inheritance hierarchy:true

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.

ccls-navigate

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")

https://www.reddit.com/r/emacs/comments/9dg13i/cclsnavigate_semantic_navigation_for_cc/

Misc

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.