Sorting completion candidates, such as sorting Org headings by level
| emacs, org
At this week's Emacs Berlin meetup, someone wanted to know how to change the order of completion candidates. Specifically, they wanted to list the top level Org Mode headings before the second level headings and so on. They were using org-ql to navigate Org headings, but since org-ql sorts its candidates by the number of matches according to the code in the org-ql-completing-read function, I wasn't quite sure how to get it to do what they wanted. (And I realized my org-ql setup was broken, so I couldn't fiddle with it live. Edit: Turns out I needed to update the peg package) Instead, I showed folks consult-org-heading which is part of the Consult package, which I like to use to jump around the headings in a single Org file. It's a short function that's easy to use as a starting point for something custom.
Here's some code that allows you to use consult-org-heading to jump to an Org heading in the current file with completions sorted by level.
(with-eval-after-load 'consult-org
(advice-add
#'consult-org--headings
:filter-return
(lambda (candidates)
(sort candidates
:lessp
(lambda (a b)
(let ((level-a (car (get-text-property 0 'consult-org--heading a)))
(level-b (car (get-text-property 0 'consult-org--heading b))))
(cond
((< level-a level-b) t)
((< level-b level-a) nil)
(t nil))))))))
My previous approach defined a different function based on consult-org-heading, but using the advice feels a little cleaner because it will also make it work for any other function that uses consult-org--headings.
I also wanted to get this to work for C-u org-refile, which uses org-refile-get-location.
This is a little trickier because the table of completion candidates is a list of cons cells that don't store the level, and it doesn't pass the metadata to completing-read to tell it not to re-sort the results. We'll just fake it by counting the number of "/", which is the path separator used if org-outline-path-complete-in-steps is set to nil.
(with-eval-after-load 'org
(advice-add
'org-refile-get-location
:around
(lambda (fn &rest args)
(let ((completion-extra-properties
'(:display-sort-function
(lambda (candidates)
(sort candidates
:key (lambda (s) (length (split-string s "/"))))))))
(apply fn args)))))
In general, if you would like completion candidates to be in a certain order, you can specify display-sort-function either by calling completing-read with a collection that's a lambda function instead of a table of completion candidates, or by overriding it with completion-category-overrides if there's a category you can use or completion-extra-properties if not.
Here's a short example of passing a lambda to a completion function (thanks to Manuel Uberti):
(defun mu-date-at-point (date)
"Insert current DATE at point via `completing-read'."
(interactive
(let* ((formats '("%Y%m%d" "%F" "%Y%m%d%H%M" "%Y-%m-%dT%T"))
(vals (mapcar #'format-time-string formats))
(opts
(lambda (string pred action)
(if (eq action 'metadata)
'(metadata (display-sort-function . identity))
(complete-with-action action vals string pred)))))
(list (completing-read "Insert date: " opts nil t))))
(insert date))
If you use consult--read from the Consult completion framework, there is a :sort property that you can set to either nil or your own function.
This entry is part of the Emacs Carnival for Feb 2026: Completion.