mirror of
https://github.com/jarun/nnn.git
synced 2024-11-24 20:01:27 +00:00
Support search as you type
This commit is contained in:
parent
8bab8e9ecb
commit
1fdcaef4f5
|
@ -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
10
nnn.1
|
@ -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
186
nnn.c
|
@ -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++;
|
||||||
|
|
Loading…
Reference in a new issue