From 88e9df01a4073706e9b8d015c31b68c2a18248d6 Mon Sep 17 00:00:00 2001 From: Arun Prakash Jana Date: Sun, 4 Jun 2017 04:49:16 +0530 Subject: [PATCH] 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 --- README.md | 17 +++++++----- config.def.h | 3 +++ nnn.1 | 32 +++++++++++++++------- nnn.c | 75 ++++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 11a20695..f406c67c 100644 --- a/README.md +++ b/README.md @@ -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 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... @@ -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 to initial dir, chdir prompt, cd ..... (with . as PWD) - Roll-over at edges, page through entries + - Navigate as you type with filters - Disk usage analyzer mode - Search - 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: -d start in detail view mode + -f start in navigate-as-you-type mode -p path to custom nlay -S start in disk usage analyzer mode -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 initial dir - | Jump to last visited dir - o | Open dir in NNN_DE_FILE_MANAGER / | Filter dir contents ^/ | Search dir in gnome-search-tool + . | Toggle hide .dot files c | Show change dir prompt d | Toggle detail view D | Toggle current file details screen + f | Toggle navigate-as-you-type mode m | Show concise mediainfo M | Show full mediainfo - . | Toggle hide .dot files s | Toggle sort by file size S | Toggle disk usage analyzer mode t | Toggle sort by modified time ! | Spawn SHELL in PWD (fallback sh) z | Run top e | Edit entry in EDITOR (fallback vi) + o | Open dir in NNN_DE_FILE_MANAGER p | Open entry in PAGER (fallback less) ^K | Invoke file name copier ^L | Force a redraw @@ -181,16 +184,18 @@ Right, Enter, l, ^M | Open file or enter dir #### 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 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 The following abbreviations are used in the detail view: diff --git a/config.def.h b/config.def.h index c5c9ce1b..943b8f3d 100644 --- a/config.def.h +++ b/config.def.h @@ -3,6 +3,7 @@ #define CURSR " > " #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 sizeorder = 0; /* Set to 1 to sort by file size */ 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, "", "" }, /* Filter */ { '/', SEL_FLTR, "", "" }, + /* Toggle filter mode */ + { 'f', SEL_MFLTR, "", "" }, /* Desktop search */ { CONTROL('_'), SEL_SEARCH, "", "" }, /* Next */ diff --git a/nnn.1 b/nnn.1 index d4972914..d80286d4 100644 --- a/nnn.1 +++ b/nnn.1 @@ -7,13 +7,15 @@ .Sh SYNOPSIS .Nm nnn .Op Ar -d +.Op Ar -f +.Op Ar -p .Op Ar -S .Op Ar -v .Op Ar -h .Op Ar PATH .Sh DESCRIPTION .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 .Nm defaults to the current directory if @@ -47,24 +49,24 @@ Change to the HOME directory Change to initial directory .It Ic - Change to the last visited directory -.It Ic o -Open directory in NNN_DE_FILE_MANAGER .It Ic / Change filter (more information below) .It Ic ^/ Search directory in gnome-search-tool +.It Ic \&. +Toggle hide .dot files .It Ic c Change into the given directory .It Ic d Toggle detail view .It Ic D Toggle current file details screen +.It Ic f +Toggle navigate-as-you-type mode .It Ic m Show concise mediainfo .It Ic M Show full mediainfo -.It Ic \&. -Toggle hide .dot files .It Ic s Toggle sort by file size .It Ic S @@ -77,6 +79,8 @@ Spawn SHELL in PWD (fallback sh) Run the system top utility. .It Ic e Open current entry in EDITOR (fallback vi) +.It Ic o +Open directory in NNN_DE_FILE_MANAGER .It Ic p Open current entry in PAGER (fallback less) .It Ic ^K @@ -100,6 +104,12 @@ supports the following options: .Fl d start in detail view mode .Pp +.Fl f + start in navigate-as-you-type mode +.Pp +.Fl p + path to custom nlay +.Pp .Fl S start in disk usage analyzer mode .Pp @@ -137,13 +147,14 @@ instructions. .Sh 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. +searching through the directory tree for a particular entry. Matching entries +are shown instantly (search-as-you-type). .Pp 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 -An empty filter expression, a search with no results or an extra backspace at -the filter prompt (like vi). +The \fIInsert\fR key, 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 @@ -153,6 +164,9 @@ use case), start the expression with a If .Nm 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 The SHELL, EDITOR and PAGER environment variables take precedence when dealing with the !, e and p commands respectively. diff --git a/nnn.c b/nnn.c index 4b002250..1e2e8317 100644 --- a/nnn.c +++ b/nnn.c @@ -77,6 +77,7 @@ xprintf(int fd, const char *fmt, ...) (((ch) >= 'a' && (ch) <= 'z') ? ((ch) - 'a' + 'A') : (ch)) #define MAX_CMD_LEN 5120 #define CURSYM(flag) (flag ? CURSR : EMPTY) +#define FILTER '/' struct assoc { char *regex; /* Regex to match on filename */ @@ -90,6 +91,7 @@ enum action { SEL_BACK, SEL_GOIN, SEL_FLTR, + SEL_MFLTR, SEL_SEARCH, SEL_NEXT, SEL_PREV, @@ -598,18 +600,21 @@ printprompt(char *str) } /* 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 -nextsel(char **run, char **env, int *ch) +nextsel(char **run, char **env, int *presel) { - int c = *ch; + int c = *presel; unsigned int i; static unsigned int len = LEN(bindings); if (c == 0) c = getch(); else - *ch = 0; + *presel = 0; + if (c == -1) idle++; else @@ -695,8 +700,8 @@ readln(char *path) char *pln = ln + 1; memset(wln, 0, LINE_MAX << 2); - wln[0] = '/'; - ln[0] = '/'; + wln[0] = FILTER; + ln[0] = FILTER; ln[1] = '\0'; cur = 0; @@ -750,7 +755,7 @@ readln(char *path) } } else { switch(*ch) { - case KEY_DC: + case KEY_DC: // fallthrough case KEY_BACKSPACE: if (len == 1) { cur = oldcur; @@ -769,6 +774,16 @@ readln(char *path) redraw(path); printprompt(ln); 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: goto end; } @@ -778,6 +793,8 @@ end: noecho(); curs_set(FALSE); timeout(1000); + + /* Return keys for navigation etc. */ return *ch; } @@ -1234,21 +1251,22 @@ show_help(void) ~ | Jump to HOME dir\n\ & | Jump to initial dir\n\ - | Jump to last visited dir\n\ - o | Open dir in NNN_DE_FILE_MANAGER\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\ d | Toggle detail view\n\ D | Toggle current file details screen\n\ + f | Toggle navigate-as-you-type mode\n\ m | Show concise mediainfo\n\ M | Show full mediainfo\n\ - . | Toggle hide .dot files\n\ s | Toggle sort by file size\n\ S | Toggle disk usage analyzer mode\n\ t | Toggle sort by modified time\n\ ! | Spawn SHELL in PWD (fallback sh)\n\ z | Run top\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\ ^K | Invoke file name copier\n\ ^L | Force a redraw\n\ @@ -1521,7 +1539,7 @@ browse(char *ipath, char *ifilter) static char fltr[LINE_MAX]; char *mime, *dir, *tmp, *run, *env; struct stat sb; - int r, fd, filtered = FALSE; + int r, fd, presel; enum action sel = SEL_RUNARG + 1; xstrlcpy(path, ipath, PATH_MAX); @@ -1529,6 +1547,12 @@ browse(char *ipath, char *ifilter) oldpath[0] = '\0'; newpath[0] = '\0'; lastdir[0] = '\0'; /* Can't move back from initial directory */ + + if (filtermode) + presel = FILTER; + else + presel = 0; + begin: if (populate(path, oldpath, fltr) == -1) { printwarn(); @@ -1542,7 +1566,7 @@ nochange: if (getppid() == 1) _exit(0); - sel = nextsel(&run, &env, &filtered); + sel = nextsel(&run, &env, &presel); switch (sel) { case SEL_CDQUIT: @@ -1583,6 +1607,8 @@ nochange: xstrlcpy(path, dir, PATH_MAX); /* Reset filter */ xstrlcpy(fltr, ifilter, LINE_MAX); + if (filtermode) + presel = FILTER; goto begin; case SEL_GOIN: /* Cannot descend in empty directories */ @@ -1621,6 +1647,8 @@ nochange: oldpath[0] = '\0'; /* Reset filter */ xstrlcpy(fltr, ifilter, LINE_MAX); + if (filtermode) + presel = FILTER; goto begin; case S_IFREG: { @@ -1667,13 +1695,20 @@ nochange: goto nochange; } case SEL_FLTR: - filtered = readln(path); + presel = readln(path); xstrlcpy(fltr, ifilter, LINE_MAX); DPRINTF_S(fltr); /* Save current */ if (ndents > 0) mkpath(path, dents[cur].name, oldpath, PATH_MAX); goto nochange; + case SEL_MFLTR: + filtermode = !filtermode; + if (filtermode) + presel = FILTER; + else + printmsg("navigate-as-you-type off"); + goto nochange; case SEL_SEARCH: exitcurses(); if (player) @@ -1844,6 +1879,8 @@ nochange: xstrlcpy(fltr, ifilter, LINE_MAX); DPRINTF_S(path); free(input); + if (filtermode) + presel = FILTER; goto begin; } case SEL_CDHOME: @@ -1869,6 +1906,8 @@ nochange: /* Reset filter */ xstrlcpy(fltr, ifilter, LINE_MAX); DPRINTF_S(path); + if (filtermode) + presel = FILTER; goto begin; case SEL_CDBEGIN: if (canopendir(ipath) == 0) { @@ -1887,6 +1926,8 @@ nochange: /* Reset filter */ xstrlcpy(fltr, ifilter, LINE_MAX); DPRINTF_S(path); + if (filtermode) + presel = FILTER; goto begin; case SEL_CDLAST: if (lastdir[0] == '\0') @@ -1904,6 +1945,8 @@ nochange: /* Reset filter */ xstrlcpy(fltr, ifilter, LINE_MAX); DPRINTF_S(path); + if (filtermode) + presel = FILTER; goto begin; case SEL_TOGGLEDOT: showhidden ^= 1; @@ -2055,6 +2098,7 @@ positional arguments:\n\ PATH directory to open [default: current dir]\n\n\ optional arguments:\n\ -d start in detail view mode\n\ + -f start in navigate-as-you-type mode\n\ -p path to custom nlay\n\ -S start in disk usage analyzer mode\n\ -v show program version and exit\n\ @@ -2079,7 +2123,7 @@ main(int argc, char *argv[]) exit(1); } - while ((opt = getopt(argc, argv, "dSp:vh")) != -1) { + while ((opt = getopt(argc, argv, "dSfp:vh")) != -1) { switch (opt) { case 'S': bsizeorder = 1; // fallthrough @@ -2088,6 +2132,9 @@ main(int argc, char *argv[]) showdetail = 1; printptr = &printent_long; break; + case 'f': + filtermode = 1; + break; case 'p': player = optarg; break;