Navigate-as-you-type support

1. Open directories in filter mode
2. Fix arrow keys not working after filter selection (due to invalid cur entry)
3. Support `Insert` key to clear filter prompt
4. Update documentation
This commit is contained in:
Arun Prakash Jana 2017-06-04 04:49:16 +05:30
parent a2e7e94064
commit 88e9df01a4
No known key found for this signature in database
GPG key ID: A75979F35C080412
4 changed files with 98 additions and 29 deletions

View file

@ -44,7 +44,7 @@ Noice is Not Noice, a noicer fork...
nnn is a fork of [noice](http://git.2f30.org/noice/), a blazing-fast lightweight terminal file browser with easy keyboard shortcuts for navigation, opening files and running tasks. noice is developed considering terminal based systems. There is no config file and mime associations are hard-coded. However, the incredible user-friendliness and speed make it a perfect utility on modern distros. nnn is a fork of [noice](http://git.2f30.org/noice/), a blazing-fast lightweight terminal file browser with easy keyboard shortcuts for navigation, opening files and running tasks. noice is developed considering terminal based systems. There is no config file and mime associations are hard-coded. However, the incredible user-friendliness and speed make it a perfect utility on modern distros.
nnn can use the default desktop opener at runtime. It also comes with `nlay` - a customizable bash script to handle media types. It adds new navigation options, enhanced DE integration, a disk usage analyzer mode, comprehensive file details and much more. Add to that a huge [performance](#performance) boost. For a detailed comparison, visit [nnn vs. noice](https://github.com/jarun/nnn/wiki/nnn-vs.-noice). nnn can use the default desktop opener at runtime. It also comes with `nlay` - a customizable bash script to handle media types. It adds new navigation options, a navigate-as-you-type mode, enhanced DE integration, a disk usage analyzer mode, comprehensive file details and much more. Add to that a huge [performance](#performance) boost. For a detailed comparison, visit [nnn vs. noice](https://github.com/jarun/nnn/wiki/nnn-vs.-noice).
If you want to edit a file in vim with some soothing music in the background while referring to a spec in your GUI PDF viewer, nnn got it! All from the same terminal session. Follow the instructions in the [quickstart](#quickstart) section and see how nnn simplifies those long desktop sessions... If you want to edit a file in vim with some soothing music in the background while referring to a spec in your GUI PDF viewer, nnn got it! All from the same terminal session. Follow the instructions in the [quickstart](#quickstart) section and see how nnn simplifies those long desktop sessions...
@ -62,6 +62,7 @@ Have fun with it! PRs are welcome. Check out [#1](https://github.com/jarun/nnn/i
- Jump HOME or to the last visited directory (as usual!) - Jump HOME or 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)
- Roll-over at edges, page through entries - Roll-over at edges, page through entries
- Navigate as you type with filters
- Disk usage analyzer mode - Disk usage analyzer mode
- Search - Search
- Filter directory contents with search-as-you-type - Filter directory contents with search-as-you-type
@ -133,6 +134,7 @@ nnn needs libreadline, libncursesw (on Linux or ncurses on OS X) and standard li
optional arguments: optional arguments:
-d start in detail view mode -d start in detail view mode
-f start in navigate-as-you-type mode
-p path to custom nlay -p path to custom nlay
-S start in disk usage analyzer mode -S start in disk usage analyzer mode
-v show program version and exit -v show program version and exit
@ -156,21 +158,22 @@ Right, Enter, l, ^M | Open file or enter dir
~ | Jump to HOME dir ~ | Jump to HOME dir
& | Jump to initial dir & | Jump to initial dir
- | Jump to last visited dir - | Jump to last visited dir
o | Open dir in NNN_DE_FILE_MANAGER
/ | Filter dir contents / | Filter dir contents
^/ | Search dir in gnome-search-tool ^/ | Search dir in gnome-search-tool
. | Toggle hide .dot files
c | Show change dir prompt c | Show change dir prompt
d | Toggle detail view d | Toggle detail view
D | Toggle current file details screen D | Toggle current file details screen
f | Toggle navigate-as-you-type mode
m | Show concise mediainfo m | Show concise mediainfo
M | Show full mediainfo M | Show full mediainfo
. | Toggle hide .dot files
s | Toggle sort by file size s | Toggle sort by file size
S | Toggle disk usage analyzer mode S | Toggle disk usage analyzer mode
t | Toggle sort by modified time t | Toggle sort by modified time
! | Spawn SHELL in PWD (fallback sh) ! | Spawn SHELL in PWD (fallback sh)
z | Run top z | Run top
e | Edit entry in EDITOR (fallback vi) e | Edit entry in EDITOR (fallback vi)
o | Open dir in NNN_DE_FILE_MANAGER
p | Open entry in PAGER (fallback less) p | Open entry in PAGER (fallback less)
^K | Invoke file name copier ^K | Invoke file name copier
^L | Force a redraw ^L | Force a redraw
@ -181,16 +184,18 @@ Right, Enter, l, ^M | Open file or enter dir
#### Filters #### Filters
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. Matching entries are shown instantly (search-as-you-type).
Filters do not stack on top of each other. They are applied anew every time. There are 3 ways to reset a filter: Filters do not stack on top of each other. They are applied anew every time. There are 4 ways to reset a filter:
An empty filter expression, a search with no results or an extra backspace at the filter prompt (like vi). The `Insert` key, 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 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.
In the navigate-as-you-type mode directories are opened in filter mode, allowing instant navigation. Works best with the arrow keys.
#### File type abbreviations #### File type abbreviations
The following abbreviations are used in the detail view: The following abbreviations are used in the detail view:

View file

@ -3,6 +3,7 @@
#define CURSR " > " #define CURSR " > "
#define EMPTY " " #define EMPTY " "
static int filtermode = 0; /* Set to 1 to enter filter mode */
static int mtimeorder = 0; /* Set to 1 to sort by time modified */ static int mtimeorder = 0; /* Set to 1 to sort by time modified */
static int sizeorder = 0; /* Set to 1 to sort by file size */ static int sizeorder = 0; /* Set to 1 to sort by file size */
static int bsizeorder = 0; /* Set to 1 to sort by blocks used including content */ static int bsizeorder = 0; /* Set to 1 to sort by blocks used including content */
@ -35,6 +36,8 @@ static struct key bindings[] = {
{ 'l', SEL_GOIN, "", "" }, { 'l', SEL_GOIN, "", "" },
/* Filter */ /* Filter */
{ '/', SEL_FLTR, "", "" }, { '/', SEL_FLTR, "", "" },
/* Toggle filter mode */
{ 'f', SEL_MFLTR, "", "" },
/* Desktop search */ /* Desktop search */
{ CONTROL('_'), SEL_SEARCH, "", "" }, { CONTROL('_'), SEL_SEARCH, "", "" },
/* Next */ /* Next */

32
nnn.1
View file

@ -7,13 +7,15 @@
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm nnn .Nm nnn
.Op Ar -d .Op Ar -d
.Op Ar -f
.Op Ar -p
.Op Ar -S .Op Ar -S
.Op Ar -v .Op Ar -v
.Op Ar -h .Op Ar -h
.Op Ar PATH .Op Ar PATH
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
(Noice is Not Noice) is a performance-optimized fork of the noice terminal file browser with improved desktop integration, navigation, disk usage analyzer mode, comprehensive file details and much more. It remains a simple and efficient file browser that stays out of your way. (Noice is Not Noice) is a performance-optimized fork of the noice terminal file browser with improved desktop integration, navigation, navigate-as-you-type mode, disk usage analyzer mode, comprehensive file details and much more. It remains a simple and efficient file browser that stays out of your way.
.Pp .Pp
.Nm .Nm
defaults to the current directory if defaults to the current directory if
@ -47,24 +49,24 @@ Change to the HOME directory
Change to initial directory Change to initial directory
.It Ic - .It Ic -
Change to the last visited directory Change to the last visited directory
.It Ic o
Open directory in NNN_DE_FILE_MANAGER
.It Ic / .It Ic /
Change filter (more information below) Change filter (more information below)
.It Ic ^/ .It Ic ^/
Search directory in gnome-search-tool Search directory in gnome-search-tool
.It Ic \&.
Toggle hide .dot files
.It Ic c .It Ic c
Change into the given directory Change into the given directory
.It Ic d .It Ic d
Toggle detail view Toggle detail view
.It Ic D .It Ic D
Toggle current file details screen Toggle current file details screen
.It Ic f
Toggle navigate-as-you-type mode
.It Ic m .It Ic m
Show concise mediainfo Show concise mediainfo
.It Ic M .It Ic M
Show full mediainfo Show full mediainfo
.It Ic \&.
Toggle hide .dot files
.It Ic s .It Ic s
Toggle sort by file size Toggle sort by file size
.It Ic S .It Ic S
@ -77,6 +79,8 @@ Spawn SHELL in PWD (fallback sh)
Run the system top utility. Run the system top utility.
.It Ic e .It Ic e
Open current entry in EDITOR (fallback vi) Open current entry in EDITOR (fallback vi)
.It Ic o
Open directory in NNN_DE_FILE_MANAGER
.It Ic p .It Ic p
Open current entry in PAGER (fallback less) Open current entry in PAGER (fallback less)
.It Ic ^K .It Ic ^K
@ -100,6 +104,12 @@ supports the following options:
.Fl d .Fl d
start in detail view mode start in detail view mode
.Pp .Pp
.Fl f
start in navigate-as-you-type mode
.Pp
.Fl p
path to custom nlay
.Pp
.Fl S .Fl S
start in disk usage analyzer mode start in disk usage analyzer mode
.Pp .Pp
@ -137,13 +147,14 @@ instructions.
.Sh FILTERS .Sh FILTERS
Filters support regexes to display only the matched Filters support regexes to display only the matched
entries in the current directory view. This effectively allows 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. Matching entries
are shown instantly (search-as-you-type).
.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. There are 3 ways to reset a filter: every time. There are 4 ways to reset a filter:
.Pp .Pp
An empty filter expression, a search with no results or an extra backspace at The \fIInsert\fR key, an empty filter expression, a search with no results or an
the filter prompt (like vi). extra backspace at the filter prompt (like vi).
.Pp .Pp
If you want to list all matches starting with the filter expression (a common If you want to list all matches starting with the filter expression (a common
use case), start the expression with a use case), start the expression with a
@ -153,6 +164,9 @@ use case), start the expression with a
If If
.Nm .Nm
is invoked as root the default filter will also match hidden files. is invoked as root the default filter will also match hidden files.
.Pp
In the navigate-as-you-type mode directories are opened in filter mode,
allowing instant navigation. Works best with the arrow keys.
.Sh ENVIRONMENT .Sh ENVIRONMENT
The SHELL, EDITOR and PAGER environment variables take precedence The SHELL, EDITOR and PAGER environment variables take precedence
when dealing with the !, e and p commands respectively. when dealing with the !, e and p commands respectively.

75
nnn.c
View file

@ -77,6 +77,7 @@ xprintf(int fd, const char *fmt, ...)
(((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch)) (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch))
#define MAX_CMD_LEN 5120 #define MAX_CMD_LEN 5120
#define CURSYM(flag) (flag ? CURSR : EMPTY) #define CURSYM(flag) (flag ? CURSR : EMPTY)
#define FILTER '/'
struct assoc { struct assoc {
char *regex; /* Regex to match on filename */ char *regex; /* Regex to match on filename */
@ -90,6 +91,7 @@ enum action {
SEL_BACK, SEL_BACK,
SEL_GOIN, SEL_GOIN,
SEL_FLTR, SEL_FLTR,
SEL_MFLTR,
SEL_SEARCH, SEL_SEARCH,
SEL_NEXT, SEL_NEXT,
SEL_PREV, SEL_PREV,
@ -598,18 +600,21 @@ 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}).
* The next keyboard input can be simulated by presel.
*/
static int static int
nextsel(char **run, char **env, int *ch) nextsel(char **run, char **env, int *presel)
{ {
int c = *ch; int c = *presel;
unsigned int i; unsigned int i;
static unsigned int len = LEN(bindings); static unsigned int len = LEN(bindings);
if (c == 0) if (c == 0)
c = getch(); c = getch();
else else
*ch = 0; *presel = 0;
if (c == -1) if (c == -1)
idle++; idle++;
else else
@ -695,8 +700,8 @@ readln(char *path)
char *pln = ln + 1; char *pln = ln + 1;
memset(wln, 0, LINE_MAX << 2); memset(wln, 0, LINE_MAX << 2);
wln[0] = '/'; wln[0] = FILTER;
ln[0] = '/'; ln[0] = FILTER;
ln[1] = '\0'; ln[1] = '\0';
cur = 0; cur = 0;
@ -750,7 +755,7 @@ readln(char *path)
} }
} else { } else {
switch(*ch) { switch(*ch) {
case KEY_DC: case KEY_DC: // fallthrough
case KEY_BACKSPACE: case KEY_BACKSPACE:
if (len == 1) { if (len == 1) {
cur = oldcur; cur = oldcur;
@ -769,6 +774,16 @@ readln(char *path)
redraw(path); redraw(path);
printprompt(ln); printprompt(ln);
break; break;
case KEY_IC:
cur = oldcur;
*ch = CONTROL('L');
goto end;
case KEY_DOWN: // fallthrough
case KEY_UP: // fallthrough
case KEY_LEFT: // fallthrough
case KEY_RIGHT:
if (len == 1)
cur = oldcur; // fallthrough
default: default:
goto end; goto end;
} }
@ -778,6 +793,8 @@ end:
noecho(); noecho();
curs_set(FALSE); curs_set(FALSE);
timeout(1000); timeout(1000);
/* Return keys for navigation etc. */
return *ch; return *ch;
} }
@ -1234,21 +1251,22 @@ show_help(void)
~ | Jump to HOME dir\n\ ~ | Jump to HOME dir\n\
& | Jump to initial dir\n\ & | Jump to initial dir\n\
- | Jump to last visited dir\n\ - | Jump to last visited dir\n\
o | Open dir in NNN_DE_FILE_MANAGER\n\
/ | Filter dir contents\n\ / | Filter dir contents\n\
^/ | Search dir in gnome-search-tool\n\ ^/ | Search dir in gnome-search-tool\n\
. | Toggle hide .dot files\n\
c | Show change dir prompt\n\ c | Show change dir prompt\n\
d | Toggle detail view\n\ d | Toggle detail view\n\
D | Toggle current file details screen\n\ D | Toggle current file details screen\n\
f | Toggle navigate-as-you-type mode\n\
m | Show concise mediainfo\n\ m | Show concise mediainfo\n\
M | Show full mediainfo\n\ M | Show full mediainfo\n\
. | Toggle hide .dot files\n\
s | Toggle sort by file size\n\ s | Toggle sort by file size\n\
S | Toggle disk usage analyzer mode\n\ S | Toggle disk usage analyzer mode\n\
t | Toggle sort by modified time\n\ t | Toggle sort by modified time\n\
! | Spawn SHELL in PWD (fallback sh)\n\ ! | Spawn SHELL in PWD (fallback sh)\n\
z | Run top\n\ z | Run top\n\
e | Edit entry in EDITOR (fallback vi)\n\ e | Edit entry in EDITOR (fallback vi)\n\
o | Open dir in NNN_DE_FILE_MANAGER\n\
p | Open entry in PAGER (fallback less)\n\ p | Open entry in PAGER (fallback less)\n\
^K | Invoke file name copier\n\ ^K | Invoke file name copier\n\
^L | Force a redraw\n\ ^L | Force a redraw\n\
@ -1521,7 +1539,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;
int r, fd, filtered = FALSE; int r, fd, presel;
enum action sel = SEL_RUNARG + 1; enum action sel = SEL_RUNARG + 1;
xstrlcpy(path, ipath, PATH_MAX); xstrlcpy(path, ipath, PATH_MAX);
@ -1529,6 +1547,12 @@ browse(char *ipath, char *ifilter)
oldpath[0] = '\0'; oldpath[0] = '\0';
newpath[0] = '\0'; newpath[0] = '\0';
lastdir[0] = '\0'; /* Can't move back from initial directory */ lastdir[0] = '\0'; /* Can't move back from initial directory */
if (filtermode)
presel = FILTER;
else
presel = 0;
begin: begin:
if (populate(path, oldpath, fltr) == -1) { if (populate(path, oldpath, fltr) == -1) {
printwarn(); printwarn();
@ -1542,7 +1566,7 @@ nochange:
if (getppid() == 1) if (getppid() == 1)
_exit(0); _exit(0);
sel = nextsel(&run, &env, &filtered); sel = nextsel(&run, &env, &presel);
switch (sel) { switch (sel) {
case SEL_CDQUIT: case SEL_CDQUIT:
@ -1583,6 +1607,8 @@ nochange:
xstrlcpy(path, dir, PATH_MAX); xstrlcpy(path, dir, PATH_MAX);
/* Reset filter */ /* Reset filter */
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
case SEL_GOIN: case SEL_GOIN:
/* Cannot descend in empty directories */ /* Cannot descend in empty directories */
@ -1621,6 +1647,8 @@ nochange:
oldpath[0] = '\0'; oldpath[0] = '\0';
/* Reset filter */ /* Reset filter */
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
case S_IFREG: case S_IFREG:
{ {
@ -1667,13 +1695,20 @@ nochange:
goto nochange; goto nochange;
} }
case SEL_FLTR: case SEL_FLTR:
filtered = readln(path); presel = readln(path);
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
DPRINTF_S(fltr); DPRINTF_S(fltr);
/* Save current */ /* Save current */
if (ndents > 0) if (ndents > 0)
mkpath(path, dents[cur].name, oldpath, PATH_MAX); mkpath(path, dents[cur].name, oldpath, PATH_MAX);
goto nochange; goto nochange;
case SEL_MFLTR:
filtermode = !filtermode;
if (filtermode)
presel = FILTER;
else
printmsg("navigate-as-you-type off");
goto nochange;
case SEL_SEARCH: case SEL_SEARCH:
exitcurses(); exitcurses();
if (player) if (player)
@ -1844,6 +1879,8 @@ nochange:
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
DPRINTF_S(path); DPRINTF_S(path);
free(input); free(input);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
} }
case SEL_CDHOME: case SEL_CDHOME:
@ -1869,6 +1906,8 @@ nochange:
/* Reset filter */ /* Reset filter */
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
DPRINTF_S(path); DPRINTF_S(path);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
case SEL_CDBEGIN: case SEL_CDBEGIN:
if (canopendir(ipath) == 0) { if (canopendir(ipath) == 0) {
@ -1887,6 +1926,8 @@ nochange:
/* Reset filter */ /* Reset filter */
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
DPRINTF_S(path); DPRINTF_S(path);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
case SEL_CDLAST: case SEL_CDLAST:
if (lastdir[0] == '\0') if (lastdir[0] == '\0')
@ -1904,6 +1945,8 @@ nochange:
/* Reset filter */ /* Reset filter */
xstrlcpy(fltr, ifilter, LINE_MAX); xstrlcpy(fltr, ifilter, LINE_MAX);
DPRINTF_S(path); DPRINTF_S(path);
if (filtermode)
presel = FILTER;
goto begin; goto begin;
case SEL_TOGGLEDOT: case SEL_TOGGLEDOT:
showhidden ^= 1; showhidden ^= 1;
@ -2055,6 +2098,7 @@ positional arguments:\n\
PATH directory to open [default: current dir]\n\n\ PATH directory to open [default: current dir]\n\n\
optional arguments:\n\ optional arguments:\n\
-d start in detail view mode\n\ -d start in detail view mode\n\
-f start in navigate-as-you-type mode\n\
-p path to custom nlay\n\ -p path to custom nlay\n\
-S start in disk usage analyzer mode\n\ -S start in disk usage analyzer mode\n\
-v show program version and exit\n\ -v show program version and exit\n\
@ -2079,7 +2123,7 @@ main(int argc, char *argv[])
exit(1); exit(1);
} }
while ((opt = getopt(argc, argv, "dSp:vh")) != -1) { while ((opt = getopt(argc, argv, "dSfp:vh")) != -1) {
switch (opt) { switch (opt) {
case 'S': case 'S':
bsizeorder = 1; // fallthrough bsizeorder = 1; // fallthrough
@ -2088,6 +2132,9 @@ main(int argc, char *argv[])
showdetail = 1; showdetail = 1;
printptr = &printent_long; printptr = &printent_long;
break; break;
case 'f':
filtermode = 1;
break;
case 'p': case 'p':
player = optarg; player = optarg;
break; break;