0

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 Answers2

0

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)))))
Rorschach
  • 31,301
  • 5
  • 78
  • 129
  • In general this project is a "learn elisp while doing project" for me, and I'm not a trained programmer. I was trying to just simplify it conceptually here. In the actual code and PR, I have a completing-read-multiple based primary read function, where the completion table candidates are of that form where car is the search string, and cdr is the key I need. That data is cached in a `bibtex-actions--candidates-cache` defvar, which is then accessed by a `bibtex-actions--get-candidates` function. So I want to reuse that for the capf as well. – Bruce D'Arcus Jun 03 '21 at 17:08
  • So, for example, I have a line like this `(let ((candidates (bibtex-actions--get-candidates)))`, which is the same for the completing-read function and for this. – Bruce D'Arcus Jun 03 '21 at 17:17
  • ok, so in that case you could just add `(let ((candidates (mapcar #'cdr (bibtex-actions...))))` IIUC. If the candidates don't change dynamically (sounds like they do, though), then store them in a variable. For more complicated dynamic updates, I think `elisp-completion-at-point` is a good reference – Rorschach Jun 03 '21 at 17:33
  • But then I'm just searching/completing on the key. In the end, I want the pop-up to show the user a list of authors, title, etc., which they can narrow against, and then once selecting one, they get back its key (because if you have thousands of references, you may not remember sufficient details to narrow a key). Is that possible? If not, I suppose a less-ideal alternative is to use an annotation or affixation function. – Bruce D'Arcus Jun 03 '21 at 17:48
  • I guess I should be explicit about this: I"m targeting `corfu` and `company-capf` for this. – Bruce D'Arcus Jun 03 '21 at 17:59
  • yea, that's a bit more involved, but either of the filtering approaches could work. Eg. have an indicator variable for the current selection type (author,etc.) and have `my-current-candidates` return the table filtered by current type. I think libraries like `helm` or `ivy` do stuff like this – Rorschach Jun 03 '21 at 18:27
  • OK, thanks for your input! Do note, however, that my mention of "author, title, etc" may have been misleading. Those are all part of the same candidate string, just as in my main post minimal example. As in, for that example, if I type "diff" I should get "jones" returned. It works well in `completing-read`, so was just assuming I could do the same in `completion-at-point`. – Bruce D'Arcus Jun 03 '21 at 19:01
  • oh, in that case see `completion-flex-all-completions` also in minibuffer.el – Rorschach Jun 03 '21 at 19:42
  • I've updated the post to reflect this discussion. Thanks again. – Bruce D'Arcus Jun 04 '21 at 15:48
0

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

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