(interzone)

Deduplicating candidates from simple CAPFs

How to avoid duplicated cape-capf-super results

By Bruno Cardoso on

When I’m writing prose, I like to have completion candidates both from related buffers and from my custom dictionary file.

The two most simple ways to achieve that with cape are either:

  1. Add cape-dabbrev and cape-dict to completion-at-point-functions, so that when the first CAPF returns nil (no candidates found), the next one tried out.
  2. Use cape-capf-super to create a custom CAPF that merges candidates from dabbrev and dict.

In the second case, a custom CAPF can be as simple as:

(defalias 'my/dabbrev-and-dict-capf
  (cape-capf-super #'cape-dabbrev #'cape-dict))

;; Alternatively, the `:with' keyword marks the CAPF as auxiliary, and
;; it only returns candidates that exists in both sources.
(defalias 'my/dabbrev-and-dict-capf
  (cape-capf-super #'cape-dabbrev :with #'cape-dict))

However, it always bothered me that cape-capf-super shows duplicate candidates when a matching string is found by both CAPFs.

Because of that, I used to go with the first option (no merging) and accept the fact that the word I’m typing could be in the dictionary, but not available for completion until dabbrev doesn’t find a candidate. Not a big deal, but it can feel a bit disruptive sometimes (“Wait. Am I even typing this right?”).

So every now and then I try to find a solution for such minor annoyances. I then found this discussion thread, where Daniel Mendler explains that “the problem is that cape-capf-super cannot disambiguate the two candidates, since they come from different sources with different properties (annotations and more importantly the :exit-function).”

With that knowledge, I realized I could just tweak my custom CAPF by setting the same properties for both of them:

(defalias 'my/dabbrev-and-dict-capf
  (cape-capf-super
   (cape-capf-properties #'cape-dabbrev
                         :annotation-function (lambda (_) " Dabbrev"))
   (cape-capf-properties #'cape-dict
                         :annotation-function (lambda (_) " Dabbrev")))
  "Dabbrev and dictionary CAPF without duplicates.")

With the same annotation function (which could also be set to nil or #'ignore), completion candidates are deduplicated. The only downside is that you won’t know from which CAPF the candidate is coming from, but that doesn’t bother me at all.

Complex CAPFs might need more tweaking (see the corfu wiki for some examples), but for simple ones it’s a great workaround.