aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md8
-rw-r--r--nnn.110
-rw-r--r--nnn.c188
3 files changed, 177 insertions, 29 deletions
diff --git a/README.md b/README.md
index cd2ed33..fbb26fa 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 ac785dd..d6eefba 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 c23e158..0ed5c7c 100644
--- 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++;