2

I'm writing a package which connects to a remote server for a game.

I want to show the list of players, along with their score, and let the player choose one of the players.

I thought of using completing-read, but I have no idea how to show a separate string in the list and have it turn into just the player name once the player has been chosen. For example, I will have to do some converting back and forth (i.e. turn Parham with experience 1440 to Parham (1440), and once I get the information, remove the bit with the parentheses so that I only get Parham).

I thought of using x-popup-dialog, but it doesn't seem to be suited to displaying players, which tend to be more than 50 items. The good thing is that it allows me to provide a ("title" . "value") cons cell for each item, which means I don't have to convert items back and forth.

Is there a way of providing a large list to a user, with extra metadata, and once they press enter on an item, get a unique value?

Parham Doustdar
  • 275
  • 2
  • 10

3 Answers3

4

What @JohnKitchin shows is a typical way to return something different from (but associated with) the candidate string that is chosen by the user.

In your case, it sounds like you might want the completion candidates (possible choices) to show more information than what is returned after choosing. E.g., you display a bunch of text describing something, and when one of those candidates is chosen you return only the associated player name.

You can do that using the typical approach, in which case completion is available against all of the displayed text for each candidate.

But if you don't want or need that, and you just want to display some extra text along with each candidate, but not make that extra text matchable, then you can annotate the completion candidates with extra, non-matchable text.

(You can still use the alist technique shown by John, if the value you ultimately want to return is something different from the candidate that is matched and chosen.)

To annotate completion candidates you can define an annotation function, which, given a displayed completion candidate (string), provides its annotation.

You then bind, around the call to completing-read, variable completion-extra-properties to a plist that contains :annotation-function and its associated value, your annotation function.

(defun foo ()
  (interactive)
  (let ((completion-extra-properties '(:annotation-function my-annot-fn)))
(completing-read "Choose: " '(("aa" . AA-value)
                  ("ab" . AB-value)
                  ("bb" . BB-value)))))

(defun my-annot-fn (candidate)
  (cdr (assoc candidate '(("aa" . " Description of AA")
              ("ab" . " Description of AB")
              ("bb" . " Description of BB")))))

The completion candidates here are the string values "aa", "ab", and "bb". Their associated annotations are, respectively, the strings " Description of AA", " Description of AB", and " Description of BB". Completion (matching etc.) is available only against the candidates, but users see the annotations right next to them.

The return value, after choosing a candidate (e.g. ab), is the value associated with it in the COLLECTION argument to completing-read. For the candidate choice ab, the return value is the Lisp symbol AB-value. (This uses the association-list technique that @JohnKitchin mentions.)

Drew
  • 75,699
  • 9
  • 109
  • 225
  • 1
    I don't see the annotations when I use this code. I think it is because I have ivy taking over the completion. If I run it with a vanilla emacs, I only see the annotation when I press tab. Otherwise the Choose minibuffer is empty, and if I use the arrows up and down I only see aa, ab, or bb (i.e. no annotations). Is that expected? – John Kitchin Jun 19 '18 at 22:16
  • Yes (good description). **That's how `completing-read` works.** If you want a `completing-read` that offers more then you need [Icicles](https://www.emacswiki.org/emacs/Icicles). It changes the behavior of `completing-read` everywhere, when you are in `icicle-mode`. Then you can optionally show completions initially or show them as soon as you type something, etc. There are lots of possibilities. Alternatively, you can make do with Ivy or Helm (without `completing-read`). – Drew Jun 20 '18 at 00:17
2

I usually do something like this:

(defun player-choice ()
  (let* ((choices '(("player 1" . 1440)
                    ("player 2" . 4400)))
     (candidates (mapcar (lambda (cell)
                   (cons (format "%s (%s)" (car cell) (cdr cell)) (car cell)))
                 choices)))
    (cdr (assoc (completing-read "Player: " candidates) candidates))))

The gist is you have the data in some form, make a list of cons cells that have (description . value) that you use in completing read. You get the descriptions for completion, and then have to look up which cell it came from with assoc, and then take the cdr of that cell.

John Kitchin
  • 11,555
  • 1
  • 19
  • 41
0

If you don't mind adding dependencies, here is another way to do it with ivy. You define a transformer for the candidate strings that constructs the string you want to use. As with @Drew's approach you can only complete on the original candidates, not in this case on the numbers). It is a feature of my earlier solution that you can do completion on the whole string.

(setq data '(("player 1" . 1440)
             ("player 2" . 4400)))

(defun player-transformer (s)
  (let ((cell (assoc s data)))
    (format "%s (%s)" s (cdr cell))))

(ivy-set-display-transformer 'player-choice 'player-transformer)

(defun player-choice ()
  (interactive)
  (ivy-read "Choice: " data
        :action (lambda (candidate) (message "%s" candidate))
        :caller 'player-choice))
John Kitchin
  • 11,555
  • 1
  • 19
  • 41