commit c5e1b205174338c1e98a50be87a2f33b9069aa0f
parent cb2c5353dc8130eb533fc96bc4fbcc1be7e8c36b
Author: Arun Prakash Jana <engineerarun@gmail.com>
Date: Sun, 22 Feb 2026 11:10:46 +0530
Support highlighting filter results
Can be disabled using make option O_NOHIGHLIGHT=1
Diffstat:
| M | Makefile | | | 5 | +++++ |
| M | src/nnn.c | | | 206 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
2 files changed, 206 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
@@ -28,6 +28,7 @@ O_NOUG := 0 # disable user, group name in status bar
O_NOX11 := 0 # disable X11 integration
O_MATCHFLTR := 0 # allow filters without matches
O_NOSORT := 0 # disable sorting entries on dir load
+O_NOHIGHLIGHT := 0 # disable filter match highlights (default: enabled)
# User patches
O_COLEMAK := 0 # change key bindings to colemak compatible layout
@@ -146,6 +147,10 @@ ifeq ($(strip $(O_NOSORT)),1)
CPPFLAGS += -DNOSORT
endif
+ifeq ($(strip $(O_NOHIGHLIGHT)),1)
+ CPPFLAGS += -DNOHIGHLIGHT_FILTER
+endif
+
ifeq ($(shell $(PKG_CONFIG) ncursesw && echo 1),1)
CFLAGS_CURSES ?= $(shell $(PKG_CONFIG) --cflags ncursesw)
LDLIBS_CURSES ?= $(shell $(PKG_CONFIG) --libs ncursesw)
diff --git a/src/nnn.c b/src/nnn.c
@@ -3159,6 +3159,105 @@ static int visible_str(const fltrexp_t *fltrexp, const char *fname)
return fnstrstr(fname, fltrexp->str) != NULL;
}
+#ifndef NOHIGHLIGHT_FILTER
+/*
+ * Get match positions for fuzzy matching
+ * Populates matched array with 1 for matched bytes, 0 otherwise
+ */
+static void fuzzy_match_positions(const char *filter, const char *fname, uchar_t *matched)
+{
+ wchar_t filter_wcs[NAME_MAX], fname_wcs[NAME_MAX];
+ size_t filter_len, fname_len;
+ bool case_insensitive = (fnstrstr == &strcasestr);
+ size_t f_idx, n_idx;
+
+ /* Clear matched array */
+ memset(matched, 0, NAME_MAX);
+
+ /* Convert multi-byte strings to wide character strings */
+ filter_len = mbstowcs(filter_wcs, filter, NAME_MAX - 1);
+ if (filter_len == (size_t)-1)
+ return;
+ filter_wcs[filter_len] = L'\0';
+
+ if (!filter_len)
+ return;
+
+ fname_len = mbstowcs(fname_wcs, fname, NAME_MAX - 1);
+ if (fname_len == (size_t)-1)
+ return;
+ fname_wcs[fname_len] = L'\0';
+
+ /* Convert to lowercase if case-insensitive matching */
+ 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;
+
+ /* Match characters in order and mark them */
+ while (f_idx < filter_len && n_idx < fname_len) {
+ if (normalize_char(filter_wcs[f_idx]) == normalize_char(fname_wcs[n_idx])) {
+ /* Mark this wide character position as matched */
+ matched[n_idx] = 1;
+ ++f_idx;
+ }
+ ++n_idx;
+ }
+}
+
+/*
+ * Get match positions for string matching (substring)
+ * Populates matched array with 1 for matched bytes, 0 otherwise
+ */
+static void string_match_positions(const char *filter, const char *fname, uchar_t *matched)
+{
+ const char *pos;
+ size_t filter_len, i;
+
+ /* Clear matched array */
+ memset(matched, 0, NAME_MAX);
+
+ filter_len = strlen(filter);
+ if (!filter_len)
+ return;
+
+ /* Find the substring */
+ pos = fnstrstr(fname, filter);
+ if (pos) {
+ /* Convert string to wide characters to track positions */
+ wchar_t fname_wcs[NAME_MAX];
+ size_t fname_wcs_len = mbstowcs(fname_wcs, fname, NAME_MAX - 1);
+ if (fname_wcs_len == (size_t)-1)
+ return;
+
+ /* Find position in wide character string */
+ size_t matched_pos = 0;
+ const char *name_ptr = fname;
+ while (name_ptr < pos && matched_pos < fname_wcs_len) {
+ int mb_len = mblen(name_ptr, MB_LEN_MAX);
+ if (mb_len <= 0)
+ break;
+ name_ptr += mb_len;
+ ++matched_pos;
+ }
+
+ /* Mark matched positions */
+ wchar_t filter_wcs[NAME_MAX];
+ size_t filter_wcs_len = mbstowcs(filter_wcs, filter, NAME_MAX - 1);
+ if (filter_wcs_len == (size_t)-1)
+ return;
+
+ for (i = 0; i < filter_wcs_len && matched_pos + i < NAME_MAX; ++i)
+ matched[matched_pos + i] = 1;
+ }
+}
+#endif
+
static int visible_fuzzy(const fltrexp_t *fltrexp, const char *fname)
{
return fuzzy_match(fltrexp->str, fname);
@@ -4527,6 +4626,107 @@ static uchar_t get_color_pair_name_ind(const struct entry *ent, char *pind, int
return C_UND;
}
+#ifndef NOHIGHLIGHT_FILTER
+static void printent_name(const struct entry *ent, uint_t namecols, bool sel)
+{
+ char * const fltr = g_ctx[cfg.curctx].c_fltr;
+
+ /* If there's a filter string, highlight matches */
+ if (fltr[1]) {
+ uchar_t matched[NAME_MAX] = {0};
+ int match_attrs = A_BOLD; /* Default: just bold for selected entries */
+
+ /* For non-selected entries, also add reverse video to matches */
+ if (!sel)
+ match_attrs |= A_REVERSE;
+
+ /* Get match positions based on filter type */
+ if (cfg.regex) {
+ /* For regex, we don't highlight - just print normally */
+#ifndef NOLC
+ addwstr(unescape(ent->name, namecols));
+#else
+ addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1));
+#endif
+ } else if (cfg.fuzzy) {
+ /* Get fuzzy match positions */
+ fuzzy_match_positions(fltr + 1, ent->name, matched);
+#ifndef NOLC
+ wchar_t * const wbuf = unescape(ent->name, namecols);
+ uint_t col = 0;
+ for (wchar_t *p = wbuf; *p && col < namecols; ++p, ++col) {
+ if (matched[col]) {
+ attron(match_attrs);
+ addch(*p);
+ attroff(match_attrs);
+ } else {
+ addch(*p);
+ }
+ }
+#else
+ /* Non-wide character version for fuzzy highlighting */
+ const char *name = unescape(ent->name, MIN(namecols, ent->nlen) + 1);
+ for (uint_t i = 0; (i < namecols) && name[i]; ++i) {
+ if (matched[i]) {
+ attron(match_attrs);
+ addch(name[i]);
+ attroff(match_attrs);
+ } else {
+ addch(name[i]);
+ }
+ }
+#endif
+ } else {
+ /* String match - highlight the substring */
+ string_match_positions(fltr + 1, ent->name, matched);
+#ifndef NOLC
+ wchar_t * const wbuf = unescape(ent->name, namecols);
+ uint_t col = 0;
+ for (wchar_t *p = wbuf; *p && col < namecols; ++p, ++col) {
+ if (matched[col]) {
+ attron(match_attrs);
+ addch(*p);
+ attroff(match_attrs);
+ } else {
+ addch(*p);
+ }
+ }
+#else
+ /* Non-wide character version for string highlighting */
+ const char *name = unescape(ent->name, MIN(namecols, ent->nlen) + 1);
+ for (uint_t i = 0; (i < namecols) && name[i]; ++i) {
+ if (matched[i]) {
+ attron(match_attrs);
+ addch(name[i]);
+ attroff(match_attrs);
+ } else {
+ addch(name[i]);
+ }
+ }
+#endif
+ }
+ } else {
+ /* No filter or filter not active - print normally */
+#ifndef NOLC
+ addwstr(unescape(ent->name, namecols));
+#else
+ addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1));
+#endif
+ }
+}
+#else
+/* Without highlight support, just print the name normally */
+static inline void printent_name(const struct entry *ent, uint_t namecols, bool sel)
+{
+ (void)sel; /* Suppress unused parameter warning */
+#ifndef NOLC
+ addwstr(unescape(ent->name, namecols));
+#else
+ addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1));
+#endif
+}
+#endif
+
static void printent(int pdents_index, uint_t namecols, bool sel)
{
const struct entry *ent = &pdents[pdents_index];
@@ -4586,11 +4786,7 @@ static void printent(int pdents_index, uint_t namecols, bool sel)
if (!ind)
++namecols;
-#ifndef NOLC
- addwstr(unescape(ent->name, namecols));
-#else
- addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1));
-#endif
+ printent_name(ent, namecols, sel);
if (attrs)
attroff(attrs);