spacemacs/layers/+lang/c-c++/funcs.el
cormacc 0a39263e2d Added clangd support. Separated lsp server selection from backend.
<<Amendment 1>>
Fixed clangd executable when c-c++-lsp-executable not set

<<Amendment 2>>
Rebased. Updated DAP configuration for compatibility with upstream dap layer
changes (specifically per-mode keybindings).

<<Amendment 3>>
Fixed DAP configuration.
Correction: declare-layers -> declare-layer-dependencies

<<Amendment 4>>
...ahem...
Fixed DAP configuration (missing '/' in func name)
2019-11-09 09:40:33 -05:00

396 lines
18 KiB
EmacsLisp
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; funcs.el --- C/C++ Layer functions File for Spacemacs
;;
;; Copyright (c) 2012-2018 Sylvain Benner & Contributors
;;
;; Author: Sylvain Benner <sylvain.benner@gmail.com>
;; URL: https://github.com/syl20bnr/spacemacs
;;
;; This file is not part of GNU Emacs.
;;
;;; License: GPLv3
(require 'cl-lib)
(require 'subr-x)
(defun spacemacs//c-toggle-auto-newline ()
"Toggle auto-newline."
(c-toggle-auto-newline 1))
;; clang
(defun spacemacs/clang-format-function (&optional style)
"Format the current function with clang-format according to STYLE."
(interactive)
(save-excursion
(c-mark-function)
(clang-format (region-beginning) (region-end) style)
(deactivate-mark) ; If the function is already formatted, then remove the mark
(message "Formatted function %s" (c-defun-name))))
(defun spacemacs/clang-format-region-or-buffer (&optional style)
"Format the current region or buffer with clang-format according to STYLE."
(interactive)
(save-excursion
(if (region-active-p)
(progn
(clang-format-region (region-beginning) (region-end) style)
(message "Formatted region"))
(progn
(clang-format-buffer style)
(message "Formatted buffer %s" (buffer-name))))))
(defun spacemacs//clang-format-on-save ()
"Format the current buffer with clang-format on save when
`c-c++-enable-clang-format-on-save' is non-nil."
(when c-c++-enable-clang-format-on-save
(spacemacs/clang-format-region-or-buffer)))
(defun spacemacs/clang-format-on-save ()
"Add before-save hook for clang-format."
(add-hook 'before-save-hook 'spacemacs//clang-format-on-save nil t))
(defun spacemacs/company-more-than-prefix-guesser ()
(spacemacs/c-c++-load-clang-args)
(company-clang-guess-prefix))
;; Based on the Sarcasm/irony-mode compilation database code.
(defun spacemacs/company-find-clang-complete-file ()
(when buffer-file-name
(let ((dir (locate-dominating-file buffer-file-name ".clang_complete")))
(when dir
(concat (file-name-as-directory dir) ".clang_complete")))))
;; Based on the Sarcasm/irony-mode compilation database code.
(defun spacemacs/company-load-clang-complete-file (cc-file)
"Load the flags from CC-FILE, one flag per line."
(let ((invocation-dir (expand-file-name (file-name-directory cc-file)))
(case-fold-search nil)
(include-regex "\\(-I\\|-isystem\\|-iquote\\|-idirafter\\)\\s-*\\(\\S-+\\)")
compile-flags)
(with-temp-buffer
(insert-file-contents cc-file)
;; Replace relative paths with absolute paths (by @trishume)
;; (goto-char (point-min))
(while (re-search-forward include-regex nil t)
(replace-match (format "%s%s" (match-string 1)
(expand-file-name (match-string 2)
invocation-dir))))
;; Turn lines into a list
(setq compile-flags
;; remove whitespaces at the end of each line, if any
(mapcar #'(lambda (line)
(if (string-match "[ \t]+$" line)
(replace-match "" t t line)
line))
(split-string (buffer-string) "\n" t))))
compile-flags))
(defun spacemacs//c-c++-get-standard-include-paths (lang)
"Returns the default system header include paths for LANG if gcc is on the
system and supports it, else returns a default set of include paths."
(let* ((start "#include <...> search starts here:")
(end "End of search list.")
(gcc-tmplt "echo | gcc -x%s -E -v - 2>&1")
(sed-tmplt " | sed -n '/%s/,/%s/{/%s/b;/%s/b;p}' | sed -e 's/^ *//g'")
(template (concat gcc-tmplt sed-tmplt))
(inc-dirs-cmd (format template lang start end start end))
(inc-dirs (split-string (shell-command-to-string inc-dirs-cmd)
"\n" t)))
(if (and inc-dirs (every 'file-exists-p inc-dirs))
inc-dirs
'("/usr/include" "/usr/local/include"))))
(defun spacemacs//filter-and-substring (flags filter-prefix substr-index)
"Returns all the strings in FLAGS starting with FILTER-PREFIX. The returned
strings are substringed from SUBSTR-INDEX inclusive to the end of the string."
(mapcar (lambda (f) (substring f substr-index))
(remove-if-not (lambda (f) (string-prefix-p filter-prefix f))
flags)))
(defun spacemacs/c-c++-load-clang-args ()
"Sets the arguments for company-clang, the system paths for company-c-headers
and the arguments for flyckeck-clang based on a project-specific text file."
(unless company-clang-arguments
(let* ((cc-file (spacemacs/company-find-clang-complete-file))
(flags (if cc-file
(spacemacs/company-load-clang-complete-file cc-file)
'()))
(i-paths (spacemacs//filter-and-substring flags
"-I" 2))
(iquote-paths (spacemacs//filter-and-substring flags
"-iquote" 7))
(isystem-paths (spacemacs//filter-and-substring flags
"-isystem" 8))
(idirafter-paths (spacemacs//filter-and-substring flags
"-idirafter" 10)))
(setq-local company-clang-arguments flags)
(setq-local flycheck-clang-args flags)
(setq-local company-c-headers-path-user
(append '(".")
iquote-paths))
(setq-local company-c-headers-path-system
(append i-paths
isystem-paths
(when (string-equal major-mode "c++-mode")
(spacemacs//c-c++-get-standard-include-paths "c++"))
(when (string-equal major-mode "c-mode")
(spacemacs//c-c++-get-standard-include-paths "c"))
idirafter-paths)))))
;; cpp-auto-include
(defalias 'spacemacs/c++-organize-includes 'cpp-auto-include)
(defun spacemacs//c++-organize-includes-on-save ()
"Organize the includes on save when `c++-enable-organize-includes-on-save'
is non-nil."
(when c++-enable-organize-includes-on-save
(spacemacs/c++-organize-includes)))
(defun spacemacs/c++-organize-includes-on-save ()
"Add before-save hook for c++-organize-includes."
(add-hook 'before-save-hook
#'spacemacs//c++-organize-includes-on-save nil t))
;; rtags
(defun spacemacs/c-c++-use-rtags (&optional useFileManager)
"Return non-nil if rtags function should be used."
;; this function is used to fallback on gtags function if rtags is not
;; supported. So if gtags layer is not used we disable the fallback by
;; returning always t.
(or (not (configuration-layer/layer-used-p 'gtags))
(and (rtags-executable-find "rc")
(cond ((not (gtags-get-rootpath)) t)
((and (not (eq major-mode 'c++-mode))
(not (eq major-mode 'c-mode))) (rtags-has-filemanager))
(useFileManager (rtags-has-filemanager))
(t (rtags-is-indexed))))))
(defun spacemacs/c-c++-tags-find-symbol-at-point (&optional prefix)
(interactive "P")
(if (and (not (rtags-find-symbol-at-point prefix))
rtags-last-request-not-indexed)
(gtags-find-tag)))
(defun spacemacs/c-c++-tags-find-references-at-point (&optional prefix)
(interactive "P")
(if (and (not (rtags-find-references-at-point prefix))
rtags-last-request-not-indexed)
(gtags-find-rtag)))
(defun spacemacs/c-c++-tags-find-symbol ()
(interactive)
(call-interactively (if (spacemacs/c-c++-use-rtags)
'rtags-find-symbol 'gtags-find-symbol)))
(defun spacemacs/c-c++-tags-find-references ()
(interactive)
(call-interactively (if (spacemacs/c-c++-use-rtags)
'rtags-find-references 'gtags-find-rtag)))
(defun spacemacs/c-c++-tags-find-file ()
(interactive)
(call-interactively (if (spacemacs/c-c++-use-rtags t)
'rtags-find-file 'gtags-find-file)))
(defun spacemacs/c-c++-tags-imenu ()
(interactive)
(call-interactively (if (spacemacs/c-c++-use-rtags t)
'rtags-imenu 'idomenu)))
;; lsp
;; -- BEGIN helper functions for common configuration of cquery and ccls backends
(defun spacemacs//c-c++-lsp-string (prefix suffix)
(concat prefix (symbol-name c-c++-lsp-server) suffix))
(defun spacemacs//c-c++-lsp-symbol (prefix suffix)
"Return a symbol for the LSP server specified by the `c-c++-lsp-server' configuration variable."
(intern (spacemacs//c-c++-lsp-string prefix suffix)))
(defun spacemacs//c-c++-lsp-call-function (prefix suffix &rest args)
(apply (spacemacs//c-c++-lsp-symbol prefix suffix) args))
(defun spacemacs//c-c++-lsp-funcall-interactively (prefix suffix &rest args)
(funcall-interactively (spacemacs//c-c++-lsp-symbol prefix suffix) args))
(defun spacemacs//c-c++-lsp-funcall-interactively-no-args (prefix suffix)
(funcall-interactively (spacemacs//c-c++-lsp-symbol prefix suffix)))
(defun spacemacs//c-c++-lsp-set-symbol (prefix suffix value)
(set (spacemacs//c-c++-lsp-symbol prefix suffix) (symbol-value value)))
(defun spacemacs//c-c++-lsp-set-config (param prefix suffix)
(when (symbol-value param) (spacemacs//c-c++-lsp-set-symbol prefix suffix param)))
(defun spacemacs//c-c++-lsp-apply-config (&rest parameters)
"Configure ccls or cquery (both take similarly named configuration parameters)."
(dolist (suffix parameters) (spacemacs//c-c++-lsp-set-config (intern (concat "c-c++-lsp-" suffix)) nil (concat "-" suffix))))
;; -- END helper functions for common configuration of cquery and ccls backends
(defun spacemacs//c-c++-lsp-apply-clangd-config ()
"Configure clangd."
(when c-c++-lsp-executable (setq lsp-clients-clangd-executable c-c++-lsp-executable))
(when c-c++-lsp-args (setq lsp-clients-clangd-args c-c++-lsp-args)))
(defun spacemacs//c-c++-lsp-config ()
"Configure the LSP server specified by the `c-c++-lsp-server' configuration variable."
(progn
(message "Configuring c-c++ lsp backend")
(spacemacs//c-c++-lsp-define-extensions)
(if (eq c-c++-lsp-server 'clangd)
(spacemacs//c-c++-lsp-apply-clangd-config)
(spacemacs//c-c++-lsp-wrap-functions)
(spacemacs//c-c++-lsp-apply-config "executable" "initialization-options" "args" "project-whitelist" "project-blacklist" "sem-highlight-method"))
;; TODO Do we still want to disable c/c++-clang if using clangd as lsp-server?
(setq-default flycheck-disabled-checkers '(c/c++-clang c/c++-gcc))
(if (eq c-c++-lsp-cache-dir nil)
(progn
(setq c-c++-lsp-cache-dir (file-truename(concat "~/.emacs.d/.cache/" (symbol-name c-c++-lsp-server))))
(message (concat "c-c++: No c-c++-lsp-cache-dir specified: defaulting to " c-c++-lsp-cache-dir))))
(ecase c-c++-lsp-server
('clangd nil)
('cquery (setq cquery-cache-dir c-c++-lsp-cache-dir)
(setq cquery-extra-args c-c++-lsp-args)
(setq cquery-extra-init-params
(if c-c++-lsp-initialization-options
(append c-c++-lsp-initialization-options '(:cacheFormat "msgpack"))
'(:cacheFormat "msgpack"))))
('ccls (setq ccls-initialization-options
(if c-c++-lsp-initialization-options
(append c-c++-lsp-initialization-options `(:cache (:directory ,c-c++-lsp-cache-dir)))
`(:cache (:directory ,c-c++-lsp-cache-dir ))))))
(when c-c++-lsp-sem-highlight-rainbow
(unless c-c++-lsp-sem-highlight-method
(progn
(setq c-c++-lsp-sem-highlight-method 'font-lock)
(message "c-c++: No semantic highlight method specified. Defaulting to `font-lock'.")))
;; This doesn't work, as can't apply (apply ...) to a macro...
;; (unless (eq c-c++-lsp-server 'clangd)
;; (spacemacs//c-c++-lsp-call-function nil "-use-default-rainbow-sem-highlight"))
(ecase c-c++-lsp-server
('clangd nil)
('cquery (cquery-use-default-rainbow-sem-highlight))
('ccls (ccls-use-default-rainbow-sem-highlight))))
(dolist (mode c-c++-modes)
(spacemacs//c-c++-lsp-bind-keys-for-mode mode))
(unless (eq c-c++-lsp-server 'clangd)
(evil-set-initial-state '(spacemacs//c-c++-lsp-symbol nil "-tree-mode") 'emacs)
;;evil-record-macro keybinding clobbers q in cquery-tree-mode-map for some reason?
(evil-make-overriding-map (symbol-value (spacemacs//c-c++-lsp-symbol nil "-tree-mode-map"))))
;; DAP layer now declared as a dependency in layers.el
(require 'dap-gdb-lldb)))
(defun spacemacs//c-c++-lsp-wrap-functions ()
"Wrap navigation functions for the LSP server specified by the `c-c++-lsp-server' configuration variable.
This is only valid for 'ccls or 'cquery."
(defun c-c++/call-hierarchy () (interactive) (spacemacs//c-c++-lsp-funcall-interactively nil "-call-hierarchy"))
(defun c-c++/call-hierarchy-inv () (interactive) (spacemacs//c-c++-lsp-funcall-interactively nil "-call-hierarchy" t))
(defun c-c++/inheritance-hierarchy () (interactive) (spacemacs//c-c++-lsp-funcall-interactively nil "-inheritance-hierarchy"))
(defun c-c++/inheritance-hierarchy-inv () (interactive) (spacemacs//c-c++-lsp-funcall-interactively nil "-inheritance-hierarchy" t))
(defun c-c++/member-hierarchy () (interactive) (spacemacs//c-c++-lsp-funcall-interactively-no-args nil "-member-hierarchy"))
(defun c-c++/preprocess-file () (interactive) (spacemacs//c-c++-lsp-funcall-interactively nil "-preprocess-file"))
(defun c-c++/refresh-index () (interactive) ()
(ecase c-c++-lsp-server
('clangd nil)
('cquery (cquery-freshen-index))
('ccls (ccls-reload)))))
(defun spacemacs//c-c++-lsp-bind-keys-for-mode (mode)
"Bind LSP backend functions for the specified mode."
(message "Binding keys for mode `%s'" mode)
;; Don't bind this -- redundant, and the current wrapper doesn't work -- not invoking correctly
;; (if (eq c-c++-lsp-server 'clangd)
;; (spacemacs/lsp-bind-extensions-for-mode mode "c-c++"
;; "o" 'clangd-other-file)
(unless (eq c-c++-lsp-server 'clangd)
(spacemacs/set-leader-keys-for-major-mode mode
;; backend
"bf" #'c-c++/refresh-index
"bp" #'c-c++/preprocess-file
;; goto
"gf" 'find-file-at-point
"gF" 'ffap-other-window
;; hierarchy
"ghc" #'c-c++/call-hierarchy
"ghC" #'c-c++/call-hierarchy-inv
"ghi" #'c-c++/inheritance-hierarchy
"ghI" #'c-c++/inheritance-hierarchy-inv
;; members
"gmh" #'c-c++/member-hierarchy)
;; goto/peek
(spacemacs/lsp-bind-extensions-for-mode mode "c-c++"
"&" 'refs-address
"R" 'refs-read
"W" 'refs-write
"c" 'callers
"C" 'callees
"v" 'vars
"hb" 'base) ;;Replace this with lsp-goto-implementation in lsp-layer?
)
(when (eq c-c++-backend 'ccls)
(spacemacs/lsp-bind-extensions-for-mode mode "c-c++"
"hd" 'derived
"mt" 'member-types
"mf" 'member-functions
"mv" 'member-vars)))
(defun spacemacs//c-c++-lsp-define-extensions ()
"Wrap some backend-specific extensions using the find functions provided by lsp-mode and lsp-ui"
(spacemacs//c-c++-lsp-call-function "spacemacs//c-c++-lsp-define-" "-extensions")
(unless (eq c-c++-lsp-server 'clangd)
(spacemacs/lsp-define-extensions "c-c++" 'vars (spacemacs//c-c++-lsp-string "$" "/vars"))))
(defun spacemacs//c-c++-lsp-define-cquery-extensions ()
(spacemacs/lsp-define-extensions "c-c++" 'refs-address
"textDocument/references"
'(plist-put (lsp--text-document-position-params) :context '(:role 128)))
(spacemacs/lsp-define-extensions "c-c++" 'refs-read
"textDocument/references"
'(plist-put (lsp--text-document-position-params) :context '(:role 8)))
(spacemacs/lsp-define-extensions "c-c++" 'refs-write
"textDocument/references"
'(plist-put (lsp--text-document-position-params) :context '(:role 16)))
(spacemacs/lsp-define-extensions "c-c++" 'callers "$cquery/callers")
(spacemacs/lsp-define-extensions "c-c++" 'callees "$cquery/callers" '(:callee t))
(spacemacs/lsp-define-extensions "c-c++" 'base "$cquery/base"))
(defun spacemacs//c-c++-lsp-define-ccls-extensions ()
(spacemacs/lsp-define-extensions "c-c++" 'refs-address "textDocument/references" '(:role 128))
(spacemacs/lsp-define-extensions "c-c++" 'refs-read "textDocument/references" '(:role 8))
(spacemacs/lsp-define-extensions "c-c++" 'refs-write "textDocument/references" '(:role 16))
(spacemacs/lsp-define-extensions "c-c++" 'callers "$ccls/call")
(spacemacs/lsp-define-extensions "c-c++" 'callees "$ccls/call" '(:callee t))
(spacemacs/lsp-define-extensions "c-c++" 'base "$ccls/inheritance")
;;ccls features without a cquery analogue...
(spacemacs/lsp-define-extensions "c-c++" 'derived "$ccls/inheritance" '(:derived t))
(spacemacs/lsp-define-extensions "c-c++" 'member-types "$ccls/member" `(:kind 2))
(spacemacs/lsp-define-extensions "c-c++" 'member-functions "$ccls/member" `(:kind 3))
(spacemacs/lsp-define-extensions "c-c++" 'member-vars "$ccls/member" `(:kind 0)))
(defun spacemacs//c-c++-lsp-define-clangd-extensions ()
;;
;; None of these work...
;; (spacemacs/lsp-define-extensions "c-c++" 'clangd-other-file "textDocument/switchSourceHeader" `(:TextDocumentIdentifier ,buffer-file-name))
;; (spacemacs/lsp-define-extensions "c-c++" 'clangd-other-file "textDocument/switchSourceHeader" '(:TextDocumentIdentifier 'buffer-file-name))
(spacemacs/lsp-define-extensions "c-c++" 'clangd-other-file "textDocument/switchSourceHeader" 'buffer-file-name)
;; (spacemacs/lsp-define-extensions "c-c++" 'clangd-other-file "textDocument/switchSourceHeader")
)
(defun spacemacs//c-c++-setup-lsp-dap ()
"Setup DAP integration."
(require 'dap-lldb))