This is a package that provides fuzzy match scoring based on the fzf algorithm by junegunn. The main contribution is a wrapper over the C implementation of fzf from the project telescope-fzf-native.nvim. Elisp functions for scoring are exported through an Emacs dynamic module.
This package does one thing – For a given STR and QUERY compute
and return a score and matching indices. If you’re looking for a fuzzy
auto-completion engine, see the Use Cases section for how this package
can be used in a completion-style.
;; Example of basic usage
(fzf-native-score "Hot-Topic" "hp")
;; (41 0 6);; Example of no match
(fzf-native-score "Hot-Topic" "2")
;; (0);; Example of slab re-use
(let ((slab (fzf-native-make-default-slab)))
(fzf-native-score "Hello World" "er" slab)
(fzf-native-score "Example of slab re-use" "xu" slab))
;; (24 1 19)See test cases for more examples.
Linux, macOS (incl. Apple silicon), and Windows are supported. Pre-built shared libraries are in the bin/ directory.
Not yet on MELPA.
Clone / download this repository and modify your load-path:
(add-to-list 'load-path (expand-file-name "/path/to/fzf-native/" user-emacs-directory)); Configuration that uses pre-built dynamic module.
(use-package fzf-native
:vc (:url "https://github.com/dangduc/fzf-native" :rev :newest)
:config
(fzf-native-load-dyn)); Configuration that builds dynamic module locally.
(use-package fzf-native
:straight
(:repo "dangduc/fzf-native"
:host github
:files (:defaults "*.c" "*.h" "*.txt"))
:init
(setq fzf-native-always-compile-module t)
:config
(fzf-native-load-own-build-dyn)); Configuration that uses pre-built dynamic module.
(use-package fzf-native
:straight
(:repo "dangduc/fzf-native"
:host github
:files (:defaults "bin"))
:config
(fzf-native-load-dyn))Fussy: fzf-native is used as
one of several choose-your-own scoring backends in fussy, a package
that provides a completing-style for intelligent matching and
sorting. See fussy’s architecture overview
for how fussy dispatches to fzf-native during all-completions.
fzfa: an Emacs
completing-read wrapper that uses fzf-native’s async surface to
stream candidates from long-running shell commands (find, rg,
git ls-files, etc.) without blocking. See
fzfa’s architecture overview
for the Elisp-side pipeline.
The C module reads the following defcustoms via symbol-value at call
time. Higher-level packages (fussy, fzfa) keep their own
user-facing defcustoms and bridge their values onto these canonical
names — fussy via setq-local (synchronous, same-buffer call
pattern), fzfa via :around advice on the C entry points
(timer-driven, cross-buffer). Set these directly only if you call
fzf-native-score / fzf-native-score-all / fzf-native-async-*
yourself; otherwise prefer the package-level knob.
| Variable | Default | Read by | Behavior |
|---|---|---|---|
fzf-native-case-mode | smart | every scoring call (sync + async) | smart (lowercase = ignore, mixed = respect) / ignore / respect |
fzf-native-batch-highlight | 25 | fzf-native-score / fzf-native-score-all | nil = off; positive N = apply completions-common-part face to top N candidates via fzf_get_positions in C. |
fzf-native-async-highlight | 200 | fzf-native-async-candidates | nil = off; t = all returned candidates; positive N = top N. |
fzf-native-max-line-length | 256 | fzf-native-async-start (once at session start) | nil = no limit; +N = drop lines longer than N; -N = truncate lines to N characters. |
fzf-native-async-cache-size | 40 | fzf-native-async-start (once at session start) | LRU result-cache entries. Each entry stores top-K results plus the full matched-candidate index for one query, enabling exact-fresh hits and prefix-refinement hits. |
fzf-native-filter-only-min-pool | 10 000 000 | fzf-native-async-start (once at session start), fzf-native-score-all (per call) | Pool-size threshold for filter-only mode. When the candidate pool reaches ≥ N, scoring swaps fzf_get_score for fzf_has_match (cheap boolean) and skips top-K sorting. nil~/~0 disables this arm. |
fzf-native-filter-only-length | nil | every scoring call (sync + async) | Query-length threshold for filter-only mode. When set to a positive integer N and the query is at most N characters, take the cheap match path. nil~/~0 disables this arm. |
fzf-native-filter-only-logic | or | every scoring call (sync + async) | How the two filter-only arms compose: or = either trigger is sufficient; and = every enabled arm must fire (a disabled arm is trivially satisfied). |
Filter-only mode replaces the per-candidate fuzzy-DP score (fzf_get_score)
with a cheap boolean match (fzf_has_match, ~5× faster) and skips the
top-K counting sort. Useful when full scoring is wasted work: very
large pools (pool-size arm) or very short queries (length arm) where
score ranking carries little signal anyway.
Two independent triggers — pool size and query length — compose via
fzf-native-filter-only-logic. Set either or both; nil~/~0 on a
defcustom disables that arm.
Sync (fzf-native-score-all): when filter-only fires, candidates are
returned in input order, no completion-score text property is
attached. Highlights still apply to the top-N entries (top by input
order in this mode).
Async (fzf-native-async-candidates): when filter-only fires, the
match-set is still built exhaustively so the per-session result cache
can refine on the next keystroke; only the cap-bound emit window
(fzf-native-async-highlight) gets re-scored + sorted so the visible
top stays ranked.
C-defined predicate that returns non-nil when filter-only mode would fire at the given query length and pool size. Reads the same three defcustoms. Single source of truth for callers that want to take consistent code paths — e.g. fussy’s metadata adjustment skips its score-based sort when this returns non-nil for the current call.
Highlighting runs entirely inside the C module: after scoring,
fzf_get_positions produces matched byte offsets, which are merged
into runs and applied to the candidate string with put-text-property
before strings are handed back to Emacs. No Elisp regex pass is
involved.
mkdir build && cmake -B build -DCMAKE_C_FLAGS='-O3 -march=native' && cmake --build buildcd Code/emacs
brew install autoconf automake pkg-config ncurses gnutls libjpeg libgif libtiff libxpm libx11 libxt libxml2
autoreconf -isvf
./autogen.sh
./configure CFLAGS="-g -O0" LDFLAGS="-g" --with-ns
make bootstrap$ pwd
~/.emacs.d/packages/emacs_31/elpaca/repos/fzf-native
$ rm -Rf build; mkdir build && cmake -B build -DCMAKE_C_FLAGS='-O0 -g -march=native' && cmake --build build$ pwd
~/Code/emacs/src
$ lldb --local-lldbinit ./emacsRefer to https://github.com/svaante/dape?tab=readme-ov-file#c-c-and-rust—lldb-dap on how to install LLDB-dap.
$ brew install llvm
($(brew --prefix --installed llvm)/bin) # PREPEND (OSX already has a binary on $PATH) to $PATH.(use-package dape
:init
;; Enable repeat mode for more ergonomic `dape' use
(use-package repeat
:ensure nil
:config
(repeat-mode))
:config
(push
'(lldb-dap
modes (c-mode c-ts-mode c++-mode c++-ts-mode)
command "lldb-dap"
command-args ["--local-lldbinit"]
ensure dape-ensure-command
:type "lldb-dap"
:cwd "/Users/james/Code/emacs/src"
:program "/Users/james/Code/emacs/src/emacs")
dape-configs)
;; Turn on global bindings for setting breakpoints with mouse
(dape-breakpoint-global-mode)
;; Info buffers to the right
(setq dape-buffer-window-arrangement 'right)
;; Info buffers like gud (gdb-mi)
(setq dape-buffer-window-arrangement 'gud)
(setq dape-info-hide-mode-line nil)
;; Pulse source line (performance hit)
(add-hook 'dape-display-source-hook 'pulse-momentary-highlight-one-line)
;; Showing inlay hints
(setq dape-inlay-hints t)
;; Save buffers on startup, useful for interpreted languages
(add-hook 'dape-start-hook (lambda () (save-some-buffers t t)))
;; Kill compile buffer on build success
(add-hook 'dape-compile-hook 'kill-buffer)
;; Projectile users
(setq dape-cwd-fn 'projectile-project-root))
;; M-x dape in fzf-native-module.c
;; Set breakpoints with mouse.For a deep dive into how fzf-native works internally — the three
scoring paths (sync single, sync batch, async streaming), the
AsyncSession threading and lock model, the arena allocator, the
score_abort same-filter rule, and the counting-sort top-K
extraction — see architecture.org.
All credit for fzf.c goes to the telescope-fzf-native.nvim project. Much credit for Emacs module binding code goes to the hotfuzz project.