From 1fdcaef4f58ec277ff45c3aed3bef469a6797bcc Mon Sep 17 00:00:00 2001 From: Arun Prakash Jana Date: Sat, 29 Apr 2017 04:32:47 +0530 Subject: [PATCH] Support search as you type --- README.md | 8 ++- nnn.1 | 10 ++- nnn.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 177 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index cd2ed33e..fbb26fa9 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Have fun with it! PRs are welcome. Check out [#1](https://github.com/jarun/nnn/i - Super-easy navigation with roll-over at edges - Jump HOME or back to the last visited directory (as usual!) - Jump to initial dir, chdir prompt, cd ..... (with . as PWD) +- Search-as-you-type - Desktop opener integration to handle mime types - Customizable bash script nlay to handle known file types - Disk usage analyzer mode @@ -66,7 +67,6 @@ Have fun with it! PRs are welcome. Check out [#1](https://github.com/jarun/nnn/i - Show media information (needs mediainfo) - Sort by modification time, size - Sort numeric names in numeric order (1, 2, ... 10, 11, ...) -- Search directory contents using regex expressions - Spawn a shell in the current directory - Invoke file path copier (*easy* shell integration) - Quit and change directory (*easy* shell integration) @@ -168,9 +168,11 @@ Right, Enter, l, ^M | Open file or enter dir Filters support regexes to display only the matched entries in the current directory view. This effectively allows searching through the directory tree for a particular entry. -Filters do not stack on top of each other. They are applied anew every time. +Filters do not stack on top of each other. They are applied anew every time. There are 3 ways to reset a filter: -An empty filter expression resets the filter. +An empty filter expression, a search with no results or an extra backspace at the filter prompt (like vi). + +If you want to list all matches starting with the filter expression (a common use case), start the expression with a `^` (caret) symbol. If nnn is invoked as root the default filter will also match hidden files. diff --git a/nnn.1 b/nnn.1 index ac785dd1..d6eefba0 100644 --- a/nnn.1 +++ b/nnn.1 @@ -138,9 +138,15 @@ entries in the current directory view. This effectively allows searching through the directory tree for a particular entry. .Pp Filters do not stack on top of each other. They are applied anew -every time. +every time. There are 3 ways to reset a filter: .Pp -An empty filter expression resets the filter. +An empty filter expression, a search with no results or an extra backspace at +the filter prompt (like vi). +.Pp +If you want to list all matches starting with the filter expression (a common +use case), start the expression with a +.Pa ^ +(caret) symbol. .Pp If .Nm diff --git a/nnn.c b/nnn.c index c23e1582..0ed5c7c9 100644 --- a/nnn.c +++ b/nnn.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #define __USE_XOPEN_EXTENDED @@ -137,6 +138,8 @@ extern int add_history(const char *); extern void add_history(const char *string); #endif +extern int wget_wch(WINDOW *, wint_t *); + /* Global context */ static struct entry *dents; static int ndents, cur, total_dents; @@ -171,6 +174,8 @@ static const char *size_units[] = {"B", "K", "M", "G", "T", "P", "E", "Z", "Y"}; static void printmsg(char *); static void printwarn(void); static void printerr(int, char *); +static int dentfind(struct entry *dents, int n, char *path); +static void redraw(char *path); static rlim_t max_openfds() @@ -561,13 +566,16 @@ printprompt(char *str) /* Returns SEL_* if key is bound and 0 otherwise. * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */ static int -nextsel(char **run, char **env) +nextsel(char **run, char **env, int *ch) { - int c; + int c = *ch; unsigned int i; static unsigned int len = LEN(bindings); - c = getch(); + if (c == 0) + c = getch(); + else + *ch = 0; if (c == -1) idle++; else @@ -582,20 +590,159 @@ nextsel(char **run, char **env) return 0; } -static char * -readln(void) +static int +fill(struct entry **dents, + int (*filter)(regex_t *, char *), regex_t *re) { - static char ln[LINE_MAX]; + static struct entry _dent; + static int count, n; + n = 0; + + for (count = 0; count < ndents; count++) { + if (filter(re, (*dents)[count].name) == 0) + continue; + + if (n != count) { + /* Copy to tmp */ + xstrlcpy(_dent.name, (*dents)[n].name, NAME_MAX); + _dent.mode = (*dents)[n].mode; + _dent.t = (*dents)[n].t; + _dent.size = (*dents)[n].size; + _dent.bsize = (*dents)[n].bsize; + + /* Copy count to n */ + xstrlcpy((*dents)[n].name, (*dents)[count].name, NAME_MAX); + (*dents)[n].mode = (*dents)[count].mode; + (*dents)[n].t = (*dents)[count].t; + (*dents)[n].size = (*dents)[count].size; + (*dents)[n].bsize = (*dents)[count].bsize; + + /* Copy tmp to count */ + xstrlcpy((*dents)[count].name, _dent.name, NAME_MAX); + (*dents)[count].mode = _dent.mode; + (*dents)[count].t = _dent.t; + (*dents)[count].size = _dent.size; + (*dents)[count].bsize = _dent.bsize; + } + + n++; + } + + return n; +} + +static int +matches(char *fltr) +{ + static regex_t re; + + /* Search filter */ + if (setfilter(&re, fltr) != 0) + return -1; + + ndents = fill(&dents, visible, &re); + qsort(dents, ndents, sizeof(*dents), entrycmp); + + return 0; +} + +static int +readln(char *path) +{ + static char ln[LINE_MAX << 2]; + static wchar_t wln[LINE_MAX]; + static wint_t ch[2] = {0}; + int r, total = ndents; + int oldcur = cur; + int len = 1; + char *pln = ln + 1; + + memset(wln, 0, LINE_MAX << 2); + wln[0] = '/'; + ln[0] = '/'; + ln[1] = '\0'; + cur = 0; timeout(-1); echo(); curs_set(TRUE); - memset(ln, 0, sizeof(ln)); - wgetnstr(stdscr, ln, sizeof(ln) - 1); + printprompt(ln); + + while ((r = wget_wch(stdscr, ch)) != ERR) { + if (r == OK) { + switch(*ch) { + case '\r': // with nonl(), this is ENTER key value + if (len == 1) { + cur = oldcur; + *ch = CONTROL('L'); + goto end; + } + + + if (matches(pln) == -1) + goto end; + + redraw(path); + goto end; + case 127: // handle DEL + if (len == 1) { + cur = oldcur; + *ch = CONTROL('L'); + goto end; + } + + if (len == 2) + cur = oldcur; + + wln[--len] = '\0'; + wcstombs(ln, wln, LINE_MAX << 2); + ndents = total; + if (matches(pln) == -1) + continue; + redraw(path); + printprompt(ln); + break; + default: + wln[len++] = (wchar_t)*ch; + wln[len] = '\0'; + wcstombs(ln, wln, LINE_MAX << 2); + ndents = total; + if (matches(pln) == -1) + continue; + redraw(path); + printprompt(ln); + } + } else if (r == KEY_CODE_YES) { + switch(*ch) { + case KEY_DC: + case KEY_BACKSPACE: + if (len == 1) { + cur = oldcur; + *ch = CONTROL('L'); + goto end; + } + + if (len == 2) + cur = oldcur; + + wln[--len] = '\0'; + wcstombs(ln, wln, LINE_MAX << 2); + ndents = total; + if (matches(pln) == -1) + continue; + redraw(path); + printprompt(ln); + break; + default: + goto end; + } + } + } +end: noecho(); curs_set(FALSE); timeout(1000); - return ln[0] ? ln : NULL; + return *ch; } static int @@ -1278,8 +1425,7 @@ browse(char *ipath, char *ifilter) static char fltr[LINE_MAX]; char *mime, *dir, *tmp, *run, *env; struct stat sb; - regex_t re; - int r, fd; + int r, fd, filtered = FALSE; enum action sel = SEL_RUNARG + 1; xstrlcpy(path, ipath, sizeof(path)); @@ -1299,7 +1445,9 @@ nochange: /* Exit if parent has exited */ if (getppid() == 1) _exit(0); - sel = nextsel(&run, &env); + + sel = nextsel(&run, &env, &filtered); + switch (sel) { case SEL_CDQUIT: { @@ -1341,7 +1489,7 @@ nochange: case SEL_GOIN: /* Cannot descend in empty directories */ if (ndents == 0) - goto nochange; + goto begin; mkpath(path, dents[cur].name, newpath, sizeof(newpath)); DPRINTF_S(newpath); @@ -1428,21 +1576,13 @@ nochange: goto nochange; } case SEL_FLTR: - /* Read filter */ - printprompt("filter: "); - tmp = readln(); - if (tmp == NULL) - tmp = ifilter; - /* Check and report regex errors */ - r = setfilter(&re, tmp); - if (r != 0) - goto nochange; - xstrlcpy(fltr, tmp, sizeof(fltr)); + filtered = readln(path); + xstrlcpy(fltr, ifilter, sizeof(fltr)); DPRINTF_S(fltr); /* Save current */ if (ndents > 0) mkpath(path, dents[cur].name, oldpath, sizeof(oldpath)); - goto begin; + goto nochange; case SEL_NEXT: if (cur < ndents - 1) cur++;