Ben Gregory's Blog

Emacs: Sane lsp-mode snippets with clangd

If you use clangd as your language server for Emacs’ lsp-mode, you may have a setup similar to the following:

(use-package lsp-mode
  :after
  yasnippet
  
  :init
  (setq lsp-keymap-prefix "C-c l"
	lsp-enable-indentation nil
	lsp-enable-on-type-formatting nil)

  :hook
  (prog-mode . lsp-deferred)	;; defer until a source code buffer is open
  (lsp-mode . lsp-enable-which-key-integration)
  
  :commands
  (lsp lsp-deferred)
  
  :config
  (define-key lsp-mode-map (kbd "C-c l") lsp-command-map)
  
  :custom
  (lsp-clangd-binary-path "/usr/bin/clangd")
  (lsp-headerline-breadcrumb-enable t)
  (lsp-modeline-code-actions-enable t)
  (lsp-enable-suggest-server-download nil)
  (lsp-warn-no-matched-clients nil)
  (lsp-idle-delay 0.1)
  (gc-cons-threshold (* 100 1024 1024))
  (read-process-output-max (* 1024 1024))
  (lsp-clients-clangd-args
   '("-j=4"
     "--header-insertion=never"
     "--all-scopes-completion"
     "--background-index"
     "--clang-tidy"
     "--compile-commands-dir=build"
     "--cross-file-rename"
     "--suggest-missing-includes")))

This works pretty nicely, especially if you use meson as your build system and define your build directory as “build” (which is where your compile_commands.json will end up).

However, I noticed that the default “snippets” recommended by clangd in the company popup menu were horribly formatted, requiring me to mess with the formatting and wasting time after insertion. It took me a while to find the root cause, but I ultimately found a solution that works much better for me, providing sane snippets for common blocks of code such as if and switch.

By default, lsp-mode uses company-capf as the completion provider (the source of the recommended items in the popup menu). This is great, but doesn’t allow us to use nice snippets available in the yasnippet-snippets package. It would be better if we could combine multiple company backends, making using of company-yasnippet as well.

Luckily this is pretty straightforward. First we need to disable the lsp-mode configurations for company by including the following line in the use-package configuration:

(setq lsp-completion-provider :none)

This will allow us to override the completion provider with multiple company backends. We also want to disable the terrible clangd snippets from showing up in our company-capf recommendations by including the following line in our lsp-mode use-package configuration:

(setq lsp-enable-snippet nil)

Our final use-package :init block for lsp-mode looks like this:

...
  :init
  (setq lsp-keymap-prefix "C-c l"
	lsp-enable-indentation nil
	lsp-enable-on-type-formatting nil
	lsp-completion-provider :none
	lsp-enable-snippet nil)
...

Now all that’s left is to configure our company backends. We can do that easily by adding the following line to our company use-package configuration:

...
  :init
  (setq company-backends '((company-capf company-yasnippet)))
...

Of course, you may want to set different backends depending on the type of major-mode you’re in, but I’ll leave that up to you to figure out. :)

Hopefully this helps you get useful snippets while using lsp-mode with clangd. Enjoy!


#Emacs #Tech

Get in touch with me at dev@mailcd.com!