Completion for use-package macro (e.g., within :custom)
I would like to have completion-at-point for use-package. In particular, it would be great if there was a way to let emacs know that inside :custom, it should expect variables, not functions, even though point might look as if it was in a funcall position. If not easily fixable, could someone guide me in the correct direction to work on this?
#277 apparently tried to do something like this, but it didn't seem to lead anywhere.
I also would like to have improved completion candidates for use-package. I actually switched away from using use-package primarily for this reason. IMHO, calling the underlying functions and macros (e.g. setopt, add-hook) provide a much better sense of discoverability and even (counterintuitively?) save me time and effort because those completions just work. Clearly I rely a lot on completions to help me figure out what I am trying to do :sweat_smile:
I guess a lot of the issue is use-package's extensive use of unquoted lists. But still, even if the lists in :custom, for example, were quoted, you would still get completion candidates for any symbol, not just variables. Callables like setopt and keymap-set offer out-of-the-box completion appropriate to the context, and they don't require everything to be contained in extraneous lists like :custom and :bind.
Since when, I think Emacs assumes that the :custom symbols are functions, not variables, and makes code jumps.
The same issue is happening in leaf and I don't know how to solve this problem.
As a work-around, for example, when there is a use-package like this,
(use-package comint
:custom
(comint-buffer-maximum-size 20000 “Increase comint buffer size.”)
(comint-prompt-read-only t “Make the prompt read only.”))
(use-package comint
:custom
(a comint-buffer-maximum-size 20000 “Increase comint buffer size.”))
(comint-prompt-read-only t “Make the prompt read only.”)))
Change it this way, and for comint-buffer-maximum-size, M-. would allow me to jump properly, but it's too bad work-around.
The default capf used in elisp-mode, elisp-completion-at-point, filters completion candidates based on context (variables, functions, ...).
The problem is that this filter cannot recognize variables in :custom of use-package.
This is not a complete solution, but I am working around the issue by setting (cape-capf-inside-code #'cape-elisp-symbol) to local variable completion-at-point-function in elisp mode.
I distinguish whether a completion candidate is a function or a variable using icon information provided by nerd-icons-corfu.
If you are using company, you can achieve the same with company-box.
Here is a part of my init.el and screenshot:
(defun my/elisp-mode-init ()
"Set completion function to cape"
(setq-local completion-at-point-functions
(list (cape-capf-inside-code #'cape-elisp-symbol))))
(add-hook 'emacs-lisp-mode-hook #'my/elisp-mode-init)
My workaround is the following:
(defun my/elisp-custom-keyword-lax-capf ()
"Provide lax completion when in s-expr with preceding :custom keyword."
(when (and (derived-mode-p 'emacs-lisp-mode)
(my/elisp-custom-keyword-lax-capf--pred))
;; get elisp-capf result
(when-let ((result (elisp-completion-at-point)))
;; capf new
(append (take 3 result)
(list :annotation-function
(lambda (cand)
(let ((sym (intern-soft cand)))
(cond
((and sym (boundp sym)) " <var>")
((and sym (fboundp sym)) " <func>")
((keywordp sym) " <key>")
(t "")))))))))
(defun my/elisp-custom-keyword-lax-capf--pred ()
"Predicate for `my/elisp-custom-keyword-lax-capf'.
Checks if the point is under `use-package' or `leaf',
and that the last keyword was :custom."
(when-let*
((limit
(save-excursion
(condition-case nil
;; go backwards-up till find use-package or leaf
(progn
(while (not (looking-at-p "(\\(use-package\\|leaf\\)\\b"))
(backward-up-list))
(point))
;; no matches
(error nil)))))
;; search backwards, find last keyword, if ":custom" ret t
(save-excursion
(when (re-search-backward " \\(:\\w+\\)" limit t)
(string= (match-string 1) ":custom")))))
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(add-hook 'completion-at-point-functions
#'my/elisp-custom-keyword-lax-capf nil t)))
I create a new capf with looser category restrictions (includes vars, funcs, etc) that activates only if:
- current mode is emacs-lisp-mode
- pseudocode:
- let
start: save starting point. - when-let
limit: searching backwards-up the s-exprs until find symboluse-package, save that point.- from point
start, up until pointlimit, search backwards until find keyword. if keyword is:custom, follow through with capf.
- from point
- let
It's important to note that the #'my/elisp-custom-keyword-lax-capf in the 'completion-at-point-functions hook needs to come before #'elisp-completion-at-point, so that the new capf has the chance to trigger before the native elisp one does.