]> Sergey Matveev's repositories - nnn.git/commitdiff
Support search as you type
authorArun Prakash Jana <engineerarun@gmail.com>
Fri, 28 Apr 2017 23:02:47 +0000 (04:32 +0530)
committerArun Prakash Jana <engineerarun@gmail.com>
Sat, 29 Apr 2017 04:22:43 +0000 (09:52 +0530)
README.md
nnn.1
nnn.c

index cd2ed33e44239469a66f83ae736cca668196c713..fbb26fa97c0159ba7c3980566539a4ad9fbcedea 100644 (file)
--- 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 ac785dd1053b90702f70815be1212a50756e849f..d6eefba02ee393a77a731417ea3fa983b469ab9b 100644 (file)
--- 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 c23e15824624454f844f6f133c3fb7a22a207714..0ed5c7c9b38926bb9c86a6c836e30dff3708db03 100644 (file)
--- a/nnn.c
+++ b/nnn.c
@@ -29,6 +29,7 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <wchar.h>
 #include <readline/readline.h>
 
 #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 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];
+       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++;