minad / cape

🦸cape.el - Completion At Point Extensions
GNU General Public License v3.0
584 stars 20 forks source link

Help needed with cape-super-capf #18

Closed vadim-zyamalov closed 2 years ago

vadim-zyamalov commented 2 years ago

Hello! Thanks for fantastic Vertico and Corfu! I'm terribly stuck attempting to merge outputs of several capf's. For example, I want to have the ability to use corfu when I try to enter a file name in the code (for example in Python open function). As I understand cape-super-capf is the thing I need. But I can't understand how to configure it properly. When I invoke corfu it show me only completion variants from lsp-mode. I have to invoke cape-file with M-x cape-file explicitly to show corfu sub-frame with filename completion variants.

I'm using lsp-mode in Emacs 27.2. I try to configure cape as follows

(use-package corfu
    :straight t
    :init
    (setq tab-always-indent 'complete)
    (corfu-global-mode)
    :custom
    (corfu-cycle t)
    (corfu-auto nil)
    (corfu-preselect-first nil)
    (corfu-preview-current t)
    (corfu-quit-no-match t)
    (corfu-quit-at-boundary t)
    :bind
    (:map corfu-map
          ("TAB" . corfu-next)
          ([tab] . corfu-next)
          ("S-TAB" . corfu-previous)
          ([backtab] . corfu-previous)))

(use-package cape
    :straight (cape :type git :host github :repo "minad/cape")
    :config
    (setq completion-at-point-functions
          (list (cape-super-capf #'cape-dabbrev #'cape-file))))

Sorry if it's a stupid question but I'm not so good at emacs configuration yet...

minad commented 2 years ago

Hello! Thanks for fantastic Vertico and Corfu!

Thanks! You are welcome!

I'm terribly stuck attempting to merge outputs of several capf's. For example, I want to have the ability to use corfu when I try to enter a file name in the code (for example in Python open function). As I understand cape-super-capf is the thing I need.

No, cape-super-capf is probably not the tool you want to use since it can only merge certain kinds of capfs. You cannot merge cape-file and lsp since the file capf is too dynamic. cape-super-capf can merge capfs like dabbrev+keyword+dict etc.

For your use cases it probably suffices to add cape-file and cape-dabbrev to the completion-at-point-functions list as described in the README. Then it should be possible to complete inside comments and string literals. For all other cases the lsp client will take over.

I suggest you don't use cape-super-capf and the other transformers for now. They are not easy to use and only useful in special scenarios. I recommend the configuration given in the README. I hope this helps!

minad commented 2 years ago

Maybe there has been a misunderstanding with regards to cape-super-capf? The purpose of the super capf is to merge multiple capfs into one. Company has a similar feature where you can group multiple backends such that the candidates appear together. This feature is quite limited and does not work with all backends, in both Cape and Company.

However it is not needed at all to use super capf to make multiple backends available for completion. The only issue is that then the completion backends are tried one by one until one of them succeeds. In the case of lsp, lsp will succeed most often. But in string literals etc the cape-file backend can take over.

I added some clarifications to https://github.com/minad/cape#super-capf---merging-multiple-capfs. Please let me know what you think.

vadim-zyamalov commented 2 years ago

However it is not needed at all to use super capf to make multiple backends available for completion. The only issue is that then the completion backends are tried one by one until one of them succeeds. In the case of lsp, lsp will succeed most often. But in string literals etc the cape-file backend can take over.

I've already tried the recipe from README. But it seems that LSP is too greedy and cape can't take over it. Well, I'll use a separate keybinding. Not so convenient as TAB but still.

I added some clarifications to https://github.com/minad/cape#super-capf---merging-multiple-capfs. Please let me know what you think.

It's more clear now, thanks!

Sorry for wasting your time!

minad commented 2 years ago

No worrries, this was an absolutely legitimate question. Btw, this is how you can turn a greedy (exclusive) capf greedy-capf into a non-exclusive non-greedy-capf:

(fset 'non-greedy-capf (cape-capf-with-properties #'greedy-capf :exclusive 'no))

You can then register the non-greedy-capf instead of the greedy-capf in the completion-at-point-functions list. Maybe this helps with the lsp mode capf.

vadim-zyamalov commented 2 years ago

Hooray, your reply pushed me towards a solution! I did the following:

Now cape-capfs are called just before lsp-completion-at-point! Look at :init and :hook sections.

(use-package lsp-mode
    ;; :ensure t
    :straight t
    :commands (lsp lsp-deferred)
    :init
    (defun my/update-completions-list ()
        (progn
            (push #'cape-dabbrev completion-at-point-functions)
            (push #'cape-file completion-at-point-functions)))
    :config
    (setq lsp-enable-file-watchers nil)
    (setq lsp-keymap-prefix "C-c l")
    (lsp-enable-which-key-integration t)
    (define-key lsp-mode-map (kbd "C-c l") lsp-command-map)
    :custom
    (lsp-completion-provider :none)
    :hook
    (lsp-completion-mode . my/update-completions-list))
minad commented 2 years ago

That's also a viable approach, however I am not sure if you prefer if cape-file/cape-dabbrev takes precedence over the lsp capf.

vadim-zyamalov commented 2 years ago

Your suggested solution works like a charm as well! Modified version of use-package init section for lsp-mode:

:init
(defun my/update-completions-list ()
    (progn
        (fset 'non-greedy-lsp (cape-capf-with-properties #'lsp-completion-at-point :exclusive 'no))
        (setq completion-at-point-functions
              '(non-greedy-lsp cape-file cape-dabbrev))))

Thanks again for your help!

ericsfraga commented 2 years ago

I've been trying some of the suggestions above but, with cape version 2022-04-28, there does not appear to be a function cape-capf-with-properties defined anywhere. What am I missing please? Thank you.

vadim-zyamalov commented 2 years ago

It is called cape-capf-properties now.

(cape-capf-properties #'lsp-completion-at-point :exclusive 'no)
ericsfraga commented 2 years ago

Thank you. I had tried that (having searched the .el file) but it doesn't seem to work for me. I have the following:

  (fset 'non-greedy-dict (cape-capf-properties #'cape-dict :exclusive 'no))
  (fset 'non-greedy-dabbrev (cape-capf-properties #'cape-dabbrev :exclusive 'no))`
  (setq-local completion-at-point-functions (list #'non-greedy-dict #'non-greedy-dabbrev #'cape-ispell))

and the behaviour is the same as the greedy version. For instance, if I swap the first two entries around, the matches found change (and are always from the first source).

minad commented 2 years ago

@ericsfraga Not sure what you are trying to achieve here, but cape-dabbrev and cape-dict are already marked as :exclusive no. So cape-capf-properties is a no-op here.

ericsfraga commented 2 years ago

Okay, that's useful to know. What I wish to achieve is to have the completion list be a union of the lists each of the cap functions would return, not just giving me the list from the first function that has matches. Is this possible?

minad commented 2 years ago

Yes, this is what cape-super-capf is for. You can combine cape-dabbrev, cape-dict and cape-keyword for example. But note that cape-super-capf is fragile and its capabilites are limited. See https://github.com/minad/cape/#super-capf---merging-multiple-capfs for details and also for the warning.

Personally I don't use cape-super-capf. I am more happy to explicitly trigger completion functions, e.g., by binding them to C-c p d for dabbrev, C-c p f for file paths etc. You can use even shorter trigger keys in your personal configuration, e.g., M-+ d, M-+ f, ...

ericsfraga commented 2 years ago

Yes, I had seen cape-super-capf and that's what I tried first. For some reason, this:

  (setq-local completion-at-point-functions
              (list (cape-super-capf #'cape-dabbrev #'cape-dict #'cape-ispell)))

does not work. When I ask for completion-at-point, I get nothing at all. Apologies if I'm missing something completely obvious here. Note I have tried without -local as well.

ericsfraga commented 2 years ago

And, yes, I note that I could have the different completion sources on different bindings. I will fall back to this if need be but would prefer to have TAB work across multiple sources if at all possible.

minad commented 2 years ago

For me it works when I evaluate the following in a scratch buffer:

  (setq-local completion-at-point-functions
              (list (cape-super-capf #'cape-dabbrev #'cape-dict)))
ericsfraga commented 2 years ago

Strange. It doesn't for me (just tried exactly this in scratch. I must have something else messing this up. Thank you for the help. If I figure it out, I will update here. Interestingly, corfu works, showing entries from more than one source.

minad commented 2 years ago

Yes, it indeed doesn't work for default completion. I don't know why. (EDIT: I pushed a fix.)