-
Notifications
You must be signed in to change notification settings - Fork 27
Use Citre together with lsp mode or eglot
lsp-mode
is an Emacs package utilizing language servers.
lsp-mode
usually does a good job indexing external libraries used by the project, which is usually not tagged in a tags file. At the same time, depends on the implementation, I've heard from lsp-mode
users that many language servers are not good at completing or finding definitions of a symbol in the project. So, these two packages could complement each other well.
I (@AmaiKinono) myself is not a lsp-mode
user. The below tricks are invented by other users, or confirmed by them to work.
Note: Citre now contains an eglot backend, and tags/global are used as its fallback. For eglot users, the following hack is no longer needed. And for lsp-mode users, you could use citre-xref-backend-to-citre-backend
to create a Citre backend from lsp-mode's xref backend, so the following hack is also not needed.
The below advice makes Citre come to rescue when enabled xref backends can't find a definition. So, when you enable the lsp backend, this tries lsp first, then use Citre.
(define-advice xref--create-fetcher (:around (-fn &rest -args) fallback)
(let ((fetcher (apply -fn -args))
(citre-fetcher
(let ((xref-backend-functions '(citre-xref-backend t)))
(apply -fn -args))))
(lambda ()
(or (with-demoted-errors "%s, fallback to citre"
(funcall fetcher))
(funcall citre-fetcher)))))
This requires lexical binding to work.
This is a completion-at-point-function
that tries lsp first, and if no completions are given, try Citre.
(defun lsp-citre-capf-function ()
"A capf backend that tries lsp first, then Citre."
(let ((lsp-result (lsp-completion-at-point)))
(if (and lsp-result
(try-completion
(buffer-substring (nth 0 lsp-result)
(nth 1 lsp-result))
(nth 2 lsp-result)))
lsp-result
(citre-completion-at-point))))
(defun enable-lsp-citre-capf-backend ()
"Enable the lsp + Citre capf backend in current buffer."
(add-hook 'completion-at-point-functions #'lsp-citre-capf-function nil t))
(add-hook 'citre-mode-hook #'enable-lsp-citre-capf-backend)
Alternatively, if you are using cape, you can merge the completion candidates from lsp and citre into one capf, the following is a example of merging citre completion at point function and eglot completion at point function together:
(defalias #'my-eglot-citre-capf
(cape-super-capf #'eglot-completion-at-point #'citre-completion-at-point))
(add-hook 'eglot-managed-mode-hook
(defun my-toggle-citre-eglot-capf ()
(if (eglot-managed-p)
(add-to-list 'completion-at-point-functions #'my-eglot-citre-capf))))
If you use company
, you can create a company backend from a Citre completion backend, then make a "combined backend" that returns both results from lsp and Citre (Thanks @yyjjl for the tip!).
Here's a macro that translates a Citre completion backend to a company backend:
(defmacro citre-backend-to-company-backend (backend)
"Create a company backend from Citre completion backend BACKEND.
The result is a company backend called
`company-citre-<backend>' (like `company-citre-tags') and can be
used in `company-backends'."
(let ((backend-name (intern (concat "company-citre-" (symbol-name backend))))
(docstring (concat "`company-mode' backend from the `"
(symbol-name backend)
"' Citre backend.\n"
"`citre-mode' needs to be enabled to use this.")))
`(defun ,backend-name (command &optional arg &rest ignored)
,docstring
(pcase command
('interactive (company-begin-backend ',backend-name))
('prefix (and (bound-and-true-p citre-mode)
(citre-backend-usable-p ',backend)
;; We shouldn't use this as it's defined for getting
;; definitions/references. But the Citre completion
;; backend design is not fully compliant with company's
;; design so there's no simple "right" solution, and this
;; works for tags/global backends.
(or (citre-get-symbol-at-point-for-backend ',backend)
'stop)))
('meta (citre-get-property 'signature arg))
('annotation (citre-get-property 'annotation arg))
('candidates (let ((citre-completion-backends '(,backend)))
(all-completions arg (nth 2 (citre-completion-at-point)))))))))
Now let's create company-citre-tags
and company-citre-global
backends, and make a combined backend of them and company-capf
, which is used by lsp-mode
:
(citre-backend-to-company-backend tags)
(citre-backend-to-company-backend global)
(setq company-backends '((company-capf
company-citre-tags
company-citre-global
:with company-yasnippet
:separate)))