Support search as you type

This commit is contained in:
Arun Prakash Jana 2017-04-29 04:32:47 +05:30
parent 8bab8e9ecb
commit 1fdcaef4f5
No known key found for this signature in database
GPG key ID: A75979F35C080412
3 changed files with 177 additions and 29 deletions

View file

@ -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 - Super-easy navigation with roll-over at edges
- Jump HOME or back to the last visited directory (as usual!) - Jump HOME or back to the last visited directory (as usual!)
- Jump to initial dir, chdir prompt, cd ..... (with . as PWD) - Jump to initial dir, chdir prompt, cd ..... (with . as PWD)
- Search-as-you-type
- Desktop opener integration to handle mime types - Desktop opener integration to handle mime types
- Customizable bash script nlay to handle known file types - Customizable bash script nlay to handle known file types
- Disk usage analyzer mode - 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) - Show media information (needs mediainfo)
- Sort by modification time, size - Sort by modification time, size
- Sort numeric names in numeric order (1, 2, ... 10, 11, ...) - Sort numeric names in numeric order (1, 2, ... 10, 11, ...)
- Search directory contents using regex expressions
- Spawn a shell in the current directory - Spawn a shell in the current directory
- Invoke file path copier (*easy* shell integration) - Invoke file path copier (*easy* shell integration)
- Quit and change directory (*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 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. If nnn is invoked as root the default filter will also match hidden files.

10
nnn.1
View file

@ -138,9 +138,15 @@ entries in the current directory view. This effectively allows
searching through the directory tree for a particular entry. searching through the directory tree for a particular entry.
.Pp .Pp
Filters do not stack on top of each other. They are applied anew 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 .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 .Pp
If If
.Nm .Nm

186
nnn.c
View file

@ -29,6 +29,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <unistd.h> #include <unistd.h>
#include <wchar.h>
#include <readline/readline.h> #include <readline/readline.h>
#define __USE_XOPEN_EXTENDED #define __USE_XOPEN_EXTENDED
@ -137,6 +138,8 @@ extern int add_history(const char *);
extern void add_history(const char *string); extern void add_history(const char *string);
#endif #endif
extern int wget_wch(WINDOW *, wint_t *);
/* Global context */ /* Global context */
static struct entry *dents; static struct entry *dents;
static int ndents, cur, total_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 printmsg(char *);
static void printwarn(void); static void printwarn(void);
static void printerr(int, char *); static void printerr(int, char *);
static int dentfind(struct entry *dents, int n, char *path);
static void redraw(char *path);
static rlim_t static rlim_t
max_openfds() max_openfds()
@ -561,13 +566,16 @@ printprompt(char *str)
/* Returns SEL_* if key is bound and 0 otherwise. /* Returns SEL_* if key is bound and 0 otherwise.
* Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */ * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
static int static int
nextsel(char **run, char **env) nextsel(char **run, char **env, int *ch)
{ {
int c; int c = *ch;
unsigned int i; unsigned int i;
static unsigned int len = LEN(bindings); static unsigned int len = LEN(bindings);
if (c == 0)
c = getch(); c = getch();
else
*ch = 0;
if (c == -1) if (c == -1)
idle++; idle++;
else else
@ -582,20 +590,159 @@ nextsel(char **run, char **env)
return 0; return 0;
} }
static char * static int
readln(void) 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); timeout(-1);
echo(); echo();
curs_set(TRUE); curs_set(TRUE);
memset(ln, 0, sizeof(ln)); printprompt(ln);
wgetnstr(stdscr, ln, sizeof(ln) - 1);
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(); noecho();
curs_set(FALSE); curs_set(FALSE);
timeout(1000); timeout(1000);
return ln[0] ? ln : NULL; return *ch;
} }
static int static int
@ -1278,8 +1425,7 @@ browse(char *ipath, char *ifilter)
static char fltr[LINE_MAX]; static char fltr[LINE_MAX];
char *mime, *dir, *tmp, *run, *env; char *mime, *dir, *tmp, *run, *env;
struct stat sb; struct stat sb;
regex_t re; int r, fd, filtered = FALSE;
int r, fd;
enum action sel = SEL_RUNARG + 1; enum action sel = SEL_RUNARG + 1;
xstrlcpy(path, ipath, sizeof(path)); xstrlcpy(path, ipath, sizeof(path));
@ -1299,7 +1445,9 @@ nochange:
/* Exit if parent has exited */ /* Exit if parent has exited */
if (getppid() == 1) if (getppid() == 1)
_exit(0); _exit(0);
sel = nextsel(&run, &env);
sel = nextsel(&run, &env, &filtered);
switch (sel) { switch (sel) {
case SEL_CDQUIT: case SEL_CDQUIT:
{ {
@ -1341,7 +1489,7 @@ nochange:
case SEL_GOIN: case SEL_GOIN:
/* Cannot descend in empty directories */ /* Cannot descend in empty directories */
if (ndents == 0) if (ndents == 0)
goto nochange; goto begin;
mkpath(path, dents[cur].name, newpath, sizeof(newpath)); mkpath(path, dents[cur].name, newpath, sizeof(newpath));
DPRINTF_S(newpath); DPRINTF_S(newpath);
@ -1428,21 +1576,13 @@ nochange:
goto nochange; goto nochange;
} }
case SEL_FLTR: case SEL_FLTR:
/* Read filter */ filtered = readln(path);
printprompt("filter: "); xstrlcpy(fltr, ifilter, sizeof(fltr));
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));
DPRINTF_S(fltr); DPRINTF_S(fltr);
/* Save current */ /* Save current */
if (ndents > 0) if (ndents > 0)
mkpath(path, dents[cur].name, oldpath, sizeof(oldpath)); mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
goto begin; goto nochange;
case SEL_NEXT: case SEL_NEXT:
if (cur < ndents - 1) if (cur < ndents - 1)
cur++; cur++;