completion-at-point function that returns the cdr

318 Views Asked by At

I need some help understanding completion-at-point.

I have this minimal example, where I want to:

  1. activate when I type "@"
  2. search/complete on candidates car ...
  3. ... but return cdr, so result at point is, for example "@doe" (though I may need to extend this later to drop the "@" in some cases, like with LaTeX).

The actual use case is to insert a citation key in a document, but search on author, title, etc. The intention is for this to be used with solutions like corfu and company-capf.

In that code, which is a front-end to bibtex-completion like helm-bibtex and ivy-bibtex, I have a core bibtex-actions-read function based on completing-read-multiple for minibuffer completion.

With this capf, I want to use the same cached data to complete against for at-point completion.

With this test example, I get 1 and 2, which is what I want on the UI end.

(defun test-capf ()
  "My capf."
  (when (looking-back "@[a-zA-Z]*")
    (list
     (save-excursion
       (backward-word)
       (point))
     (point)
     (lambda (str pred action)
       (let ((candidates '(("a title doe" . "doe")
                           ("different title jones" . "jones")
                           ("nothing smith" . "smith"))))
       (complete-with-action action candidates str pred))))))

But how do I adapt it to this to add 3? That is, if I type "@not", corfu or company should display "nothing smith", and if I select that item, it should return "@smith" at-point.

Note: my package pretty much depends on completion-styles like orderless, so order is of course not significant.

Do I need to use an :exit-function here?

For completeness, here's the current actual function, which now says "no matches" when I try to use it.

(defun bibtex-actions-complete-key-at-point ()
    "Complete citation key at point.

When inserting '@' in a buffer the capf UI will present user with
a list of entries, from which they can narrow against a string
which includes title, author, etc., and then select one. This
function will then return the key 'key', resulting in '@key' at
point."
    ;; FIX current function only returns "no match"
    ;; TODO this regex needs to adapt for mode/citation syntax
  (when (looking-back "@[a-zA-Z]+" 5)
    (let* ((candidates (bibtex-actions--get-candidates))
           (begin (save-excursion (backward-word) (point)))
           (end (point)))
      (list begin end candidates :exclusive 'no
            ;; I believe I need an exit-function so I can insert the key instead
            ;; of the candidate string.
            :exit-function
            (lambda (chosen status)
              (when (eq status 'finished)
                (cdr (assoc chosen candidates))))))))

Any other tips or suggestions?

This Q&A is related, but I can't figure out how to adapt it.

2

There are 2 best solutions below

9
Rorschach On

Why not just keep the completion candidates in your completion table, not conses?

There are some useful wrappers in minibuffer.el around completion tables. In this case you could use completion-table-dynamic, as a wrapper to use a function as the COLLECTION argument to complete-with-action.

I think the more efficient way would just collect the cdrs of your current candidates and allow the C implementations of all-completions to find matches

(complete-with-action action (mapcar #'cdr candidates) str pred)

Or, calling a function to return current candidates

(completion-table-dynamic
 (lambda (_str)
   (mapcar #'cdr (my-current-candidates))))

Or, filtering in elisp

(let ((candidates '((...)))
      (beg '...)
      (end '...))
  ;; ...
  (list beg end
        (completion-table-dynamic
         (lambda (str)
           (cl-loop for (a . b) in candidates
                    if (string-prefix-p str a)
                    collect b)))))
0
Bruce D'Arcus On

The solution was an exit-function, with body like this:

(delete-char (- (length str)))
(insert (cdr (assoc str candidates)))))