nnn

mirror of https://github.com/jarun/nnn
Log | Files | Refs | README | LICENSE

commit 1fd752d2005b4e211e03ee303277879de879b537
parent 722fc524dce801e362abc24b034085e6fabd9e69
Author: Arun Prakash Jana <engineerarun@gmail.com>
Date:   Fri, 27 Mar 2026 22:08:49 +0530

Add fuzzy scoring logic

Diffstat:
Msrc/nnn.c | 87++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 86 insertions(+), 1 deletion(-)

diff --git a/src/nnn.c b/src/nnn.c @@ -3264,6 +3264,86 @@ static int visible_fuzzy(const fltrexp_t *fltrexp, const char *fname) } static int (*filterfn)(const fltrexp_t *fltr, const char *fname) = &visible_str; +static int (*entrycmpfn)(const void *va, const void *vb); +static const char *fuzzy_sort_fltr; + +static int fuzzy_match_score(const char *filter, const char *fname) +{ + wchar_t filter_wcs[NAME_MAX], fname_wcs[NAME_MAX]; + size_t match_pos[NAME_MAX]; + size_t filter_len, fname_len, f_idx, n_idx; + bool case_insensitive = (fnstrstr == &strcasestr); + + filter_len = mbstowcs(filter_wcs, filter, NAME_MAX - 1); + if (filter_len == (size_t)-1) + return INT_MAX; + filter_wcs[filter_len] = L'\0'; + + if (!filter_len) + return 0; + + fname_len = mbstowcs(fname_wcs, fname, NAME_MAX - 1); + if (fname_len == (size_t)-1) + return INT_MAX; + fname_wcs[fname_len] = L'\0'; + + if (case_insensitive) { + for (size_t i = 0; i < filter_len; ++i) + filter_wcs[i] = towlower(filter_wcs[i]); + for (size_t i = 0; i < fname_len; ++i) + fname_wcs[i] = towlower(fname_wcs[i]); + } + + f_idx = 0; + n_idx = 0; + + while (f_idx < filter_len && n_idx < fname_len) { + if (normalize_char(filter_wcs[f_idx]) == normalize_char(fname_wcs[n_idx])) { + match_pos[f_idx] = n_idx; + ++f_idx; + } + ++n_idx; + } + + if (f_idx != filter_len) + return INT_MAX; + + { + size_t first = match_pos[0], endpos = match_pos[filter_len - 1], span = (endpos - first) + 1; + size_t gaps = span - filter_len, consec = 0, word_starts = 0; + + for (size_t i = 0; i < filter_len; ++i) { + if (i > 0 && match_pos[i] == match_pos[i - 1] + 1) + ++consec; + + if (match_pos[i] == 0 || normalize_char(fname_wcs[match_pos[i] - 1]) == L' ') + ++word_starts; + } + + return (int)(gaps * 1000 + first * 100 + (span - consec) * 10 - word_starts * 5); + } +} + +static int fuzzyentrycmp(const void *va, const void *vb) +{ + const struct entry *pa = (const struct entry *)va; + const struct entry *pb = (const struct entry *)vb; + int sa, sb; + + if ((pb->flags & DIR_OR_DIRLNK) != (pa->flags & DIR_OR_DIRLNK)) { + if (pb->flags & DIR_OR_DIRLNK) + return 1; + return -1; + } + + sa = fuzzy_match_score(fuzzy_sort_fltr, pa->name); + sb = fuzzy_match_score(fuzzy_sort_fltr, pb->name); + + if (sa != sb) + return cfg.reverse ? (sa < sb ? 1 : -1) : (sa < sb ? -1 : 1); + + return entrycmpfn(va, vb); +} static void clearfilter(void) { @@ -3584,7 +3664,12 @@ static int matches(const char *fltr) regfree(&re); #endif - ENTSORT(pdents, ndents, entrycmpfn); + if (cfg.fuzzy && fltr[0]) { + fuzzy_sort_fltr = fltr; + ENTSORT(pdents, ndents, fuzzyentrycmp); + fuzzy_sort_fltr = NULL; + } else + ENTSORT(pdents, ndents, entrycmpfn); return ndents; }