0

I've got lsp mode, projectile mode, and flymake modes, working pleasantly for C++ running using a clangd language server. However, I have many, many directories that (at least for now) do not have compile_commands.json files in a dominating directory above the C++ source files. I would like to avoid seeing error markups (from flymake which relies upon the c/c++-gcc and c/c++-clangd checkers) because lsp-clangd server starts anyhow, and uses some builtin default of /usr/bin/clangd with some arbitrary compile options, but just disable using that server. Now, in the source directories, there might be other languages of different forms, and any specific server for those other languages are ok to start servers for. My clangd lives in a different directory than in /usr/bin for reasons that are irrelevant to this question.

So, what configuration option do I need in order to tell lsp to avoid using the clangd server at at all for C++ files in those directories that lack a parent directory containing the compile_commands.el file? I do know how to advise functions like lsp or lsp-deferred to make it conditional by using locate-dominating-file but that seems like smashing a gnat with a size 11 sledge.

I've poured through the lsp online help already and edebug traced through some of the functions, but nothing is jumping out as the simple solution that avoids the cost of advising the lsp functions/entry-points. Kinda lookin' for a hook function somewhere either in lsp or projectile mode. I've also perused the lsp-clangd.el file for hooks and whatnot, but again came up empty.

Versions:

LSP :: lsp-mode 20220213.906, Emacs 27.2, gnu/linux
Projectile Version: 20220211.932  (`M-x projectile-version` returns nil ... huh? Whuh?)

1 Answer 1

1

Rationale

It is unreasonable for developers to expect either of lsp-mode or flymake-mode Elisp modes to predict all of the arcane C/C++ compiler options necessary for basic checks such as location of #include-ed header files that are defined on a per-application basis with -I<directory> options. Thus, when those Elisp modes attempt to do so without a compile_commands.json file (compilation database in LSP parlance), they will get it "wrong", leading to flymake errors and failure of lsp-mode to xref to definitions and references to symbols in the language (i.e., here the focus is C/C++).

I found that two things were required in my self-answer here. I'm not satisfied with this answer as I'm expecting a rebuttal from someone who can simplify this sledge-hammered solution:

  1. Disabling of lsp-mode when there is no dominating compile_commands.json file relative to the source file being opened, and,
  2. Disabling of flycheck-mode under the same conditions as above for lsp-mode.

Conditional lsp-mode

;; There is nothing in lsp-mode that defines the
;; bg-clangd-compilation-database-file constant. Perhaps it is only known by the
;; LSP protocol and hidden from client view by the clangd implementation:
(defvar bg-clangd-compilation-database-file "compile_commands.json"
  "Basename of the compilation database used by clangd.")

(defun bg-clangd-is-compilation-database-file-available ()
  "Return t when there is a compilation database file in a dominating directory."
  (and (derived-mode-p 'c++-mode)
       (locate-dominating-file default-directory bg-clangd-compilation-database-file)))

(defun bg-lsp-deferred-when-compilation-database-file-available ()
  "Only call lsp-deferred if there is a compilation database file in a dominating directory.

There is no need to start lsp using default clangd server if
there is no compilation database file in a dominating directory.
This is because there is always a need to have clangd see the
_exact_ set of compilation options as used in building the
application, and the defaults are useless, and flood the buffer
with error markers (due to flymake, dealt with separately than
this function here), all due to a missing compilation database
file (`bg-clangd-compilation-database-file', which see)."
  (when (derived-mode-p 'c++-mode)
    (if (not (bg-clangd-is-compilation-database-file-available))
    (message "No compilation database file found relative to %S which is needed for lsp mode configuration." default-directory)
      ;; For lsp-deffered, see
      ;; https://emacs-lsp.github.io/lsp-mode/page/installation/#:~:text=which%2Dkey%2Dmode))-,To%20defer%20LSP%20server,-startup%20(and%20DidOpen
      (lsp-deferred))))

Then add the hook:

(use-package lsp-mode
  :ensure t
  :hook ((c++-mode . bg-lsp-deferred-when-compilation-database-file-available))
  :commands (lsp lsp-deferred))

Conditional Flycheck

Conditionally disable the flycheck on those C/C++ files that do not have a dominating compile_commands.json file (i.e., the file does not exist in the current working directory of the source file, nor in any parent directory):

(defun bg-flycheck-conditionally-enable-cxx-checkers ()
  "Only enable flycheck on C/C++ files if there is a compilation database given by `bg-clangd-compilation-database-file'.

Similar rationale as given in `bg-lsp-deferred-when-compilation-database-file-available', which see."
  (unless (bg-clangd-is-compilation-database-file-available)
    (mapc (lambda (elem) (add-to-list 'flycheck-disabled-checkers elem))
          '(c/c++-clang c/c++-gcc))))

Then add the hook:

(use-package flycheck
  :ensure t

  :config

  ;; Only enable flycheck on C/C++ files under certain conditions
  (add-hook 'c++-mode-hook 'bg-flycheck-conditionally-enable-cxx-checkers)  

  :init
  (global-flycheck-mode))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.