Table of Contents
Associating Data with a CandidateSometimes, one wishes to select a candidate from a list, but actually use data associated with the candidate instead of the candidate itself.
For example, consider the Swiper-like command in which a user searches for a matching line. Although users choose a candidate line based on the candidate's text, the command actually needs the candidate's line number, not the text of said line, in order to jump to the right place.
There are potentially many ways to associate data with a candidate, but here are a few:
Add text properties to the candidate.
One way to use this approach is to add properties using the function propertize
, and then retrieve that property from the selected candidate using get-text-property
. One shortcoming to this approach is that completing-read
tends to remove text properties when completing. selectrum--read
does not have that limitation, but by using it, your command is no longer compatible with other completion frameworks, such as Icomplete.
This situation might improve in the future in Emacs 28, using the new minibuffer-allow-text-properties
.
;; Add the property, such as with `mapcar'. (propertize "some candidate" 'my-property my-data) ;; ... Later, retrieve the property. (get-text-property 0 'my-property selected-candidate)
Use an alist
of candidate-data pairs.
An alist is a list of key-value pairs. When passed an alist, completing-read
will automatically use the first item in the list as the candidate. Once a candidate is selected, you can get its associated data from the alist using the functions assoc
(which gets the first pair for a given key) and cdr
(to get the value of the association).
When comparing string keys, remember to use one of
equal
, which assoc
uses by defaultstring-equal
, which is equivalent to equal
, but raises an error when arguments aren't strings or symbolsequal-including-properties
, which also considers text properties, unlike equal
and string-equal
.When using this approach, keep in mind that your candidates' contents must be unique, as text properties tend to be stripped and assoc
will only return the first matching key-value pair.
(let* ((my-pairs ...) (chosen-candidate "Choose: " my-pairs) (associated-data (cdr (assoc chosen-candidate my-pairs)))) ...)
Format the candidate to include the extra information.
In the case of jumping to matching lines, this might mean prepending the line number to the front of the candidate, such as in ("Line 1: This is my first line of text." "Line 2: This is the second line.")
. The data could then be extracted using the function substring
and parsing functions like string-to-number
.
Although simple, this approach does have its downsides. For example, in the Swiper-like command, if each candidate includes a line number, then it becomes harder to search for numbers that are actually in the buffer. Since any part of the candidate can be matched against, this can result in many false positives.
Completion meta-data provides Emacs with extra information about the candidates, such as their annotations or how they should be sorted.
For some features, Selectrum has its own internal way of expressing the same information that is described by completion metadata, and these ways are often simpler, but using completion metadata should work with all completion interfaces (such as Helm, Ivy, Icomplete, and the default completion UI). When writing your commands to use metadata instead of package-specific features, all Emacs users can benefit from your work.
The function complete-with-action
can be used to simplify writing your own completion tables (see examples below).
See the Emacs Lisp reference manual on Programmed Completion for more information.
An annotation is text displayed next to a candidate. An annotation is not part of its respective candidate, and does not affect the return value of completion functions like completing-read
. For example, Selectrum shows a function's documentation string when completing function names with completion-at-point
. You can see this by doing the following:
eval-expression
(M-:
) so that you can type a Lisp expression.(complet
. This makes it unambiguous that we are typing a function name.completion-at-point
(M-TAB
or C-M-i
) to complete the function name.In Emacs's default completion UI, annotations are shown next to the candidate in the *Completetions*
buffer. In Selectrum, they are shown to the right of the candidate in the minibuffer (or in the case of commands that use completion-in-region
, at the right margin) on the same line as the candidate.
When using Selectrum-specific features, we have 2 main options:
selectrum-candidate-display-suffix
.selectrum-candidate-display-right-margin
.See Selectrum's README.md for more information about these and other text properties that Selectrum uses.
(completing-read "Display some text to the right of candidate: " (list (propertize "my candidate" 'selectrum-candidate-display-suffix (propertize " - My candidate suffix." 'face 'completions-annotations)))) (completing-read "Display some text at right margin: " (list (propertize "my candidate" 'selectrum-candidate-display-right-margin (propertize "Text at right margin" 'face 'completions-annotations))))
Instead of relying on Selectrum-specific features, one could also use the annotation-function
property of completion metadata. This property should be a function that takes a string candidate and returns a string to display after the candidate. There are two main consequences of this approach:
(completing-read "Use annotations to note which is longest: " (lambda (input predicate action) (if (eq action 'metadata) `(metadata (annotation-function . (lambda (str) (when (string= str "longest") " <- This is the longest.")))) (complete-with-action action '("longest" "short" "longer") input predicate))))
Generally, candidates are sorted using the function found in the variable selectrum-preprocess-candidates-function
and according the value of selectrum-should-sort
. This is not the same as moving the default candidate to the top of the list, which is determined by the no-move-default-candidate
parameter of selectrum--read
.
In a custom Selectrum command, you can disable the sorting of candidates (independent of whether the default candidate will be moved) by wrapping your completing code in a let
expression and setting selectrum-should-sort
to nil
. By default, Selectrum sorts candidates by length, displaying shorter candidates at the top of the list.
(let ((selectrum-should-sort nil)) (completing-read "Not sorted by length: " '("longest" "short" "longer")))
A more general method of controlling sorting (which should work everywhere) is to set the display-sort-function
property in your candidates' completion metadata. This property is a function that takes a list of candidates, and returns a sorted list. Therefore, one can use the function identity
to disable sorting, because it will return the list it receives.
(completing-read "Not sorted: " (lambda (input predicate action) (if (eq action 'metadata) `(metadata (display-sort-function . identity)) (complete-with-action action '("longest" "short" "longer") input predicate))))
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4