flex-x is an Emacs completion style built on the built-in flex
completion style.
- Order-independent flex AND filtering for whitespace-separated terms.
- Literal matching for completion tables which explicitly preserve candidate order, avoiding noisy fuzzy matches when score sorting is disabled.
- Sort by minibuffer history, then flex match quality.
- Respect completion metadata which explicitly disables additional sorting.
- Highlight matches with standard completion faces.
- Add a regexp expander for non-ASCII candidates, for example migemo or pyim.
flex-x keeps the standard completion UI while adding flex AND-style filtering
for whitespace-separated terms, history-aware ranking, match highlighting, and
optional regexp expansion for non-ASCII candidates. These screenshots use the
built-in fido-vertical-mode UI and the current commentary theme configuration
from init.org; flex-x itself does not depend on either.
flex-x is available from MELPA. After enabling the MELPA
package archive, install and configure it with use-package. The
icomplete-minibuffer-setup-hook binding mirrors the current init.org
configuration used for the screenshots:
(use-package icomplete
:ensure nil
:init
(fido-vertical-mode))
(use-package flex-x
:ensure t
:config
(add-hook 'icomplete-minibuffer-setup-hook
(lambda ()
(setq-local completion-styles '(flex-x basic))))
(setopt completion-styles '(flex-x basic)))Some completion categories have built-in completion-category-overrides
which can prepend or replace styles for that category. If you want
flex-x to be preferred for those categories too, configure category
overrides explicitly:
(setopt completion-category-overrides
'((buffer (styles . (flex-x basic substring)))
(project-file (styles . (flex-x substring)))))For migemo-style matching, pass migemo-get-pattern directly:
(with-eval-after-load 'migemo
(setq flex-x-extra-pattern-function #'migemo-get-pattern))For pyim-style matching:
(with-eval-after-load 'pyim
(require 'pyim-cregexp-utils nil t)
(setq flex-x-extra-pattern-function #'pyim-cregexp-build))By default, expanded regexp matching is used only for candidates containing
non-ASCII characters. Customize flex-x-extra-match-nonascii-only if
you want it to run for every candidate.
Every non-empty whitespace-separated term uses built-in flex matching, and every term must match the candidate. Terms may occur in any order. A configured regexp expander is used as an alternative when built-in flex does not match a term. Input containing only whitespace does not filter candidates.
If completion metadata sets a display or cycle sort function to identity,
flex-x preserves the candidate order for that sort channel and uses literal
matching even before a space is entered. This avoids returning large unsorted
fuzzy match sets. The other sort channel continues to use its own metadata
function or flex match quality.
In a completion minibuffer, flex-x displays [Fuzzy] or [Literal]
immediately after the prompt. The indicator reflects the current matching
mode even when there are no matching candidates. It is part of the protected
minibuffer prompt, so it does not become part of the completion input.
When minibuffer completion does not provide an explicit sort function, flex-x first sorts by flex match quality and then stably promotes matching candidates from the active minibuffer history. Candidates outside the history keep their flex order. Explicit metadata sort functions and completion outside the minibuffer do not receive this history promotion.
Expanded regexp matching scans up to flex-x-extra-match-candidate-limit
raw candidates for the current completion prefix. The default limit is
5000; when it is exceeded, flex-x stops predicate-driven enumeration
and skips regexp expansion. Set it to nil if you want migemo or
pyim expansion to scan every candidate even in very large completion
tables. Ignored filename extensions are filtered after regexp expansion,
so an ignored file remains available when it is the only match.
Corfu normally uses a completion table’s display-sort-function instead of
corfu-sort-function. Since flex-x provides a display sort function, enabling
corfu-history-mode alone does not combine Corfu history sorting with flex-x
sorting. Use the
combined sorting configuration from the official Corfu wiki
to apply both:
(with-eval-after-load 'corfu
(corfu-history-mode 1)
(defun my-corfu-combined-sort (candidates)
"Sort CANDIDATES using both display and Corfu sort functions."
(let ((candidates
(let ((display-sort-function
(corfu--metadata-get 'display-sort-function)))
(if display-sort-function
(funcall display-sort-function candidates)
candidates))))
(if corfu-sort-function
(funcall corfu-sort-function candidates)
candidates)))
(setopt corfu-sort-override-function #'my-corfu-combined-sort))This makes Corfu history sorting the final sorting layer. Since
corfu-sort-override-function deliberately overrides completion metadata, it
also reorders tables whose display-sort-function is identity. Omit this
override where preserving the completion table’s exact order is more important
than Corfu history sorting.
flex-x-extra-pattern-function can be nil, one function, or one function
name. The function receives a search term and should return a regexp string or
nil.
Examples:
(setq flex-x-extra-pattern-function #'migemo-get-pattern)FEATURES.md defines the package behavior boundary.
make test
make compile
make check