Fix nnn.vim #82: support picker plugins

Plugins like fzopen have the capability to pick files.
This comes handy if nnn is executing as a file picker.

This is a 2-way communication:

- nnn sends the picker output file to plugin ("-" for stdout)
- the plugin tells nnn if it has overwritten the output file
This commit is contained in:
Arun Prakash Jana 2021-05-17 00:55:29 +05:30
parent 715abc7a3f
commit cbc4587630
No known key found for this signature in database
GPG key ID: A75979F35C080412
4 changed files with 108 additions and 64 deletions

6
nnn.1
View file

@ -419,6 +419,8 @@ separated by \fI;\fR:
x:_chmod +x $nnn | Make the hovered file executable
y:-_sync* | Flush cached writes
----------------------------------- + -------------------------------------------------
Online docs: https://github.com/jarun/nnn/tree/master/plugins
.Ed
.Pp
\fBNNN_COLORS:\fR string of color numbers for each context, e.g.:
@ -503,10 +505,10 @@ separated by \fI;\fR:
1. Overridden by a temporary path with -a option.
2. If the FIFO file doesn't exist it will be created,
but not removed (unless it is generated by -a option).
Online docs: https://github.com/jarun/nnn/wiki/Live-previews
.Ed
.Pp
.Em https://github.com/jarun/nnn/wiki/Live-previews
.Pp
\fBNNN_LOCKER:\fR terminal locker program.
.Bd -literal
export NNN_LOCKER='bmon -p wlp1s0'

View file

@ -27,7 +27,7 @@ Plugins extend the capabilities of `nnn`. They are _executable_ scripts (or bina
| [finder](finder) | Run custom find command and list | sh | - |
| [fzcd](fzcd) | Change to the directory of a fuzzy-selected file/dir | sh | fzf |
| [fzhist](fzhist) | Fuzzy-select a cmd from history, edit in `$EDITOR` and run | sh | fzf, mktemp |
| [fzopen](fzopen) | Fuzzy find a file in dir subtree and edit or open | sh | fzf, xdg-open |
| [fzopen](fzopen) | Fuzzy find file(s) in subtree to edit/open/pick | sh | fzf, xdg-open |
| [fzplug](fzplug) | Fuzzy find, preview and run other plugins | sh | fzf |
| [fzz](fzz) | Change to any directory in the z database with fzf | sh | fzf, z |
| [getplugs](getplugs) | Update plugins to installed `nnn` version | sh | curl |
@ -188,9 +188,10 @@ Notes:
When `nnn` executes a plugin, it does the following:
- Changes to the directory where the plugin is to be run (`$PWD` pointing to the active directory)
- Passes two arguments to the script:
- Passes three arguments to the script:
1. The hovered file's name.
2. The working directory (might differ from `$PWD` in case of symlinked paths; non-canonical).
3. The picker mode output file (`-` for stdout) if `nnn` is executed as a file picker.
- Sets the environment variable `NNN_PIPE` used to control `nnn` active directory.
Plugins can also read the `.selection` file in the config directory.
@ -221,6 +222,7 @@ The `opcode` indicates the operation type.
|:---:| --- |
| `c` | change directory |
| `l` | list files in list mode |
| `p` | picker file overwritten |
For convenience, we provided a helper script named `.nnn-plugin-helper` and a function named `nnn_cd` to ease this process. `nnn_cd` receives the path to change to as the first argument, and the context as an optional second argument.
If a context is not provided, it is asked for explicitly. To skip this and choose the current context, set the `CUR_CTX` variable in `.nnn-plugin-helper` (or in the specific plugin after sourcing `.nnn-plugin-helper`) to 1.

View file

@ -1,14 +1,24 @@
#!/usr/bin/env sh
# Description: Fuzzy find a file in directory subtree
# Opens in $VISUAL or $EDITOR if text
# Opens other type of files with xdg-open
# Description: Regular mode:
# Fuzzy find a file in directory subtree.
# Opens in $VISUAL or $EDITOR if text.
# Opens other type of files with xdg-open.
# Work only with a single file selected.
#
# Picker mode:
# If picker mode output file is passed, it
# will be overwritten with any picked files.
# Leaves untouched if no file is picked.
# Works with single/multiple files selected.
#
# Dependencies: fd/find, fzf/skim, xdg-open
#
# Shell: POSIX compliant
# Author: Arun Prakash Jana
. "$(dirname "$0")"/.nnn-plugin-helper
if type fzf >/dev/null 2>&1; then
cmd="$FZF_DEFAULT_COMMAND"
if type fd >/dev/null 2>&1; then
@ -16,7 +26,7 @@ if type fzf >/dev/null 2>&1; then
else
[ -z "$cmd" ] && cmd="find . -type f 2>/dev/null"
fi
entry="$(eval "$cmd" | fzf --delimiter / --nth=-1 --tiebreak=begin --info=hidden)"
entry="$(eval "$cmd" | fzf -m --delimiter / --nth=-1 --tiebreak=begin --info=hidden)"
# To show only the file name
# entry=$(find . -type f 2>/dev/null | fzf --delimiter / --with-nth=-1 --tiebreak=begin --info=hidden)
elif type sk >/dev/null 2>&1; then
@ -25,6 +35,23 @@ else
exit 1
fi
# Check for picker mode
if [ "$3" ]; then
if [ "$entry" ]; then
if [ "-" = "$3" ]; then
printf "%s\n" "$entry"
else
printf "%s\n" "$entry" > "$3"
fi
# Tell `nnn` to clear its internal selection
printf "%s" "0p" > "$NNN_PIPE"
fi
exit 0
fi
# Open the file (works for a single file only)
case "$(file -biL "$entry")" in
*text*)
"${VISUAL:-$EDITOR}" "$entry" ;;

123
src/nnn.c
View file

@ -176,7 +176,7 @@
#define TMP_LEN_MAX 64
#define DOT_FILTER_LEN 7
#define ASCII_MAX 128
#define EXEC_ARGS_MAX 8
#define EXEC_ARGS_MAX 10
#define LIST_FILES_MAX (1 << 16)
#define SCROLLOFF 3
@ -775,7 +775,7 @@ static haiku_nm_h haiku_hnd;
/* Forward declarations */
static void redraw(char *path);
static int spawn(char *file, char *arg1, char *arg2, uchar_t flag);
static int spawn(char *file, char *arg1, char *arg2, char *arg3, uchar_t flag);
static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
static void move_cursor(int target, int ignore_scrolloff);
static char *load_input(int fd, const char *path);
@ -1051,7 +1051,7 @@ static char *getgrname(gid_t gid)
static inline bool getutil(char *util)
{
return spawn("which", util, NULL, F_NORMAL | F_NOTRACE) == 0;
return spawn("which", util, NULL, NULL, F_NORMAL | F_NOTRACE) == 0;
}
/*
@ -1431,7 +1431,7 @@ static bool listselfile(void)
return FALSE;
snprintf(g_buf, CMD_LEN_MAX, "tr \'\\0\' \'\\n\' < %s", selpath);
spawn(utils[UTIL_SH_EXEC], g_buf, NULL, F_CLI | F_CONFIRM);
spawn(utils[UTIL_SH_EXEC], g_buf, NULL, NULL, F_CLI | F_CONFIRM);
return TRUE;
}
@ -1550,7 +1550,7 @@ static void endselection(void)
}
snprintf(buf, sizeof(buf), patterns[P_REPLACE], listpath, listroot, g_tmpfpath);
spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI);
fd = open(g_tmpfpath, O_RDONLY);
if (fd == -1) {
@ -1628,7 +1628,7 @@ static int editselection(void)
}
mtime = sb.st_mtime;
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, F_CLI);
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI);
fd = open(g_tmpfpath, O_RDONLY);
if (fd == -1) {
@ -1746,7 +1746,7 @@ static void export_file_list(void)
DPRINTF_S(strerror(errno));
}
spawn(editor, g_tmpfpath, NULL, F_CLI);
spawn(editor, g_tmpfpath, NULL, NULL, F_CLI);
if (xconfirm(get_input(messages[MSG_RM_TMP])))
unlink(g_tmpfpath);
@ -2001,9 +2001,9 @@ static int join(pid_t p, uchar_t flag)
/*
* Spawns a child process. Behaviour can be controlled using flag.
* Limited to 2 arguments to a program, flag works on bit set.
* Limited to 3 arguments to a program, flag works on bit set.
*/
static int spawn(char *file, char *arg1, char *arg2, uchar_t flag)
static int spawn(char *file, char *arg1, char *arg2, char *arg3, uchar_t flag)
{
pid_t pid;
int status = 0, retstatus = 0xFFFF;
@ -2013,9 +2013,13 @@ static int spawn(char *file, char *arg1, char *arg2, uchar_t flag)
if (!file || !*file)
return retstatus;
/* Swap args if the first arg is NULL and second isn't */
/* Swap args if the first arg is NULL and the other 2 aren't */
if (!arg1 && arg2) {
arg1 = arg2;
if (arg3) {
arg2 = arg3;
arg3 = NULL;
} else
arg2 = NULL;
}
@ -2030,7 +2034,7 @@ static int spawn(char *file, char *arg1, char *arg2, uchar_t flag)
xstrsncpy(cmd, file, len);
status = parseargs(cmd, argv);
if (status == -1 || status > (EXEC_ARGS_MAX - 3)) { /* arg1, arg2 and last NULL */
if (status == -1 || status > (EXEC_ARGS_MAX - 4)) { /* 3 args and last NULL */
free(cmd);
DPRINTF_S("NULL or too many args");
return retstatus;
@ -2040,6 +2044,7 @@ static int spawn(char *file, char *arg1, char *arg2, uchar_t flag)
argv[status] = arg1;
argv[++status] = arg2;
argv[++status] = arg3;
if (flag & F_NORMAL)
exitcurses();
@ -2147,11 +2152,11 @@ static bool xrm(char *fpath)
if (!rm_opts[1])
return FALSE;
spawn("rm", rm_opts, fpath, F_NORMAL | F_CHKRTN);
spawn("rm", rm_opts, fpath, NULL, F_NORMAL | F_CHKRTN);
} else if (g_state.trash == 1)
spawn("trash-put", fpath, NULL, F_NORMAL);
spawn("trash-put", fpath, NULL, NULL, F_NORMAL);
else
spawn("gio trash", fpath, NULL, F_NORMAL | F_MULTI);
spawn("gio trash", fpath, NULL, NULL, F_NORMAL | F_MULTI);
return (access(fpath, F_OK) == -1); /* File is removed */
}
@ -2184,7 +2189,7 @@ static bool cpmv_rename(int choice, const char *path)
/* selsafe() returned TRUE for this to be called */
if (!selbufpos) {
snprintf(buf, sizeof(buf), "tr '\\0' '\\n' < %s > %s", selpath, g_tmpfpath);
spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI);
count = lines_in_file(fd, buf, sizeof(buf));
if (!count)
@ -2195,9 +2200,9 @@ static bool cpmv_rename(int choice, const char *path)
close(fd);
snprintf(buf, sizeof(buf), patterns[P_CPMVFMT], g_tmpfpath);
spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI);
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, F_CLI);
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI);
fd = open(g_tmpfpath, O_RDONLY);
if (fd == -1)
@ -2212,7 +2217,7 @@ static bool cpmv_rename(int choice, const char *path)
}
snprintf(buf, sizeof(buf), patterns[P_CPMVRNM], path, g_tmpfpath, cmd);
if (!spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI | F_CHKRTN))
if (!spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI | F_CHKRTN))
ret = TRUE;
finish:
if (fd >= 0)
@ -2259,7 +2264,7 @@ static bool cpmvrm_selection(enum action sel, char *path)
}
}
if (sel != SEL_CPMVAS && spawn(utils[UTIL_SH_EXEC], g_buf, NULL, F_CLI | F_CHKRTN)) {
if (sel != SEL_CPMVAS && spawn(utils[UTIL_SH_EXEC], g_buf, NULL, NULL, F_CLI | F_CHKRTN)) {
printmsg(messages[MSG_FAILED]);
return FALSE;
}
@ -2314,7 +2319,7 @@ static bool batch_rename(void)
if (dir) /* Don't retain dir entries in selection */
selbufpos = 0;
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, F_CLI);
spawn((cfg.waitedit ? enveditor : editor), g_tmpfpath, NULL, NULL, F_CLI);
/* Reopen file descriptor to get updated contents */
fd2 = open(g_tmpfpath, O_RDONLY);
@ -2330,7 +2335,7 @@ static bool batch_rename(void)
}
snprintf(buf, sizeof(buf), batchrenamecmd, foriginal, g_tmpfpath);
spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI);
spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI);
ret = TRUE;
finish:
@ -2380,7 +2385,7 @@ static void archive_selection(const char *cmd, const char *archive, const char *
selpath, curpath, cmd, archive
#endif
);
spawn(utils[UTIL_SH_EXEC], buf, NULL, F_CLI | F_CONFIRM);
spawn(utils[UTIL_SH_EXEC], buf, NULL, NULL, F_CLI | F_CONFIRM);
free(buf);
}
@ -4057,7 +4062,7 @@ static char *get_output(char *buf, const size_t bytes, const char *file,
/* Show in pager in child */
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
spawn(pager, NULL, NULL, F_CLI);
spawn(pager, NULL, NULL, NULL, F_CLI);
_exit(EXIT_SUCCESS);
}
@ -4135,7 +4140,7 @@ static bool show_stats(const char *fpath, const struct stat *sb)
fclose(fp);
close(fd);
spawn(pager, g_tmpfpath, NULL, F_CLI);
spawn(pager, g_tmpfpath, NULL, NULL, F_CLI);
unlink(g_tmpfpath);
return TRUE;
}
@ -4186,7 +4191,7 @@ static void handle_archive(char *fpath, char op)
}
if (op == 'x') /* extract */
spawn(util, arg, fpath, F_NORMAL);
spawn(util, arg, fpath, NULL, F_NORMAL);
else /* list */
get_output(NULL, 0, util, arg, fpath, TRUE);
}
@ -4338,7 +4343,7 @@ static bool archive_mount(char *newpath)
/* Mount archive */
DPRINTF_S(name);
DPRINTF_S(newpath);
if (spawn(cmd, name, newpath, F_NORMAL)) {
if (spawn(cmd, name, newpath, NULL, F_NORMAL)) {
printmsg(messages[MSG_FAILED]);
return FALSE;
}
@ -4403,12 +4408,12 @@ static bool remote_mount(char *newpath)
/* Connect to remote */
if (opt == 's') {
if (spawn(env, tmp, newpath, flag)) {
if (spawn(env, tmp, newpath, NULL, flag)) {
printmsg(messages[MSG_FAILED]);
return FALSE;
}
} else {
spawn(env, tmp, newpath, flag);
spawn(env, tmp, newpath, NULL, flag);
printmsg(messages[MSG_RCLONE_DELAY]);
xdelay(XDELAY_INTERVAL_MS << 2); /* Set 4 times the usual delay */
}
@ -4471,19 +4476,19 @@ static bool unmount(char *name, char *newpath, int *presel, char *currentpath)
}
#if defined (__APPLE__) || defined (__FreeBSD__)
if (spawn(cmd, newpath, NULL, F_NORMAL)) {
if (spawn(cmd, newpath, NULL, NULL, F_NORMAL)) {
#else
if (spawn(cmd, "-u", newpath, F_NORMAL)) {
if (spawn(cmd, "-u", newpath, NULL, F_NORMAL)) {
#endif
if (!xconfirm(get_input(messages[MSG_LAZY])))
return FALSE;
#ifdef __APPLE__
if (spawn(cmd, "-l", newpath, F_NORMAL)) {
if (spawn(cmd, "-l", newpath, NULL, F_NORMAL)) {
#elif defined (__FreeBSD__)
if (spawn(cmd, "-f", newpath, F_NORMAL)) {
if (spawn(cmd, "-f", newpath, NULL, F_NORMAL)) {
#else
if (spawn(cmd, "-uz", newpath, F_NORMAL)) {
if (spawn(cmd, "-uz", newpath, NULL, F_NORMAL)) {
#endif
printwait(messages[MSG_FAILED], presel);
return FALSE;
@ -4500,7 +4505,7 @@ static bool unmount(char *name, char *newpath, int *presel, char *currentpath)
static void lock_terminal(void)
{
spawn(xgetenv("NNN_LOCKER", utils[UTIL_LOCKER]), NULL, NULL, F_CLI);
spawn(xgetenv("NNN_LOCKER", utils[UTIL_LOCKER]), NULL, NULL, NULL, F_CLI);
}
static void printkv(kv *kvarr, FILE *fp, uchar_t max, uchar_t id)
@ -4658,7 +4663,7 @@ static void show_help(const char *path)
fclose(fp);
close(fd);
spawn(pager, g_tmpfpath, NULL, F_CLI);
spawn(pager, g_tmpfpath, NULL, NULL, F_CLI);
unlink(g_tmpfpath);
}
@ -4680,7 +4685,7 @@ static bool run_cmd_as_plugin(const char *file, char *runfile, uchar_t flags)
else
runfile = NULL;
spawn(g_buf, runfile, NULL, flags);
spawn(g_buf, runfile, NULL, NULL, flags);
return TRUE;
}
@ -4704,7 +4709,7 @@ static void rmlistpath(void)
if (listpath) {
DPRINTF_S(__func__);
DPRINTF_S(listpath);
spawn("rm -rf", listpath, NULL, F_NOTRACE | F_MULTI);
spawn("rm -rf", listpath, NULL, NULL, F_NOTRACE | F_MULTI);
/* Do not free if program was started in list mode */
if (listpath != initpath)
free(listpath);
@ -4756,14 +4761,16 @@ static void readpipe(int fd, char **path, char **lastname, char **lastdir)
if (len <= 0)
return;
/* Terminate the path read */
g_buf[len] = '\0';
g_buf[len] = '\0'; /* Terminate the path read */
nextpath = g_buf;
} else if (op == 'l') {
/* Remove last list mode path, if any */
rmlistpath();
rmlistpath(); /* Remove last list mode path, if any */
nextpath = load_input(fd, *path);
} else if (op == 'p') {
free(selpath);
selpath = NULL;
clearselection();
g_state.picker = 0;
}
if (nextpath) {
@ -4829,14 +4836,20 @@ static bool run_selected_plugin(char **path, const char *file, char *runfile, ch
_exit(EXIT_FAILURE);
if (!cmd_as_plugin) {
char *sel = NULL;
char std[2] = "-";
/* Generate absolute path to plugin */
mkpath(plgpath, file, g_buf);
if (g_state.picker)
sel = selpath ? selpath : std;
if (runfile && runfile[0]) {
xstrsncpy(*lastname, runfile, NAME_MAX);
spawn(g_buf, *lastname, *path, 0);
spawn(g_buf, *lastname, *path, sel, 0);
} else
spawn(g_buf, NULL, *path, 0);
spawn(g_buf, NULL, *path, sel, 0);
} else
run_cmd_as_plugin(file, runfile, flags);
@ -4867,7 +4880,7 @@ static bool plugscript(const char *plugin, uchar_t flags)
{
mkpath(plgpath, plugin, g_buf);
if (!access(g_buf, X_OK)) {
spawn(g_buf, NULL, NULL, flags);
spawn(g_buf, NULL, NULL, NULL, flags);
return TRUE;
}
@ -4887,7 +4900,7 @@ static bool launch_app(char *newpath)
}
if (tmp && *tmp) // NOLINT
spawn(tmp, (r == F_NORMAL) ? "0" : NULL, NULL, r);
spawn(tmp, (r == F_NORMAL) ? "0" : NULL, NULL, NULL, r);
return FALSE;
}
@ -4911,7 +4924,7 @@ static bool prompt_run(const char *current)
#endif
if (tmp && *tmp) { // NOLINT
ret = TRUE;
spawn(shell, "-c", tmp, F_CLI | F_CONFIRM);
spawn(shell, "-c", tmp, NULL, F_CLI | F_CONFIRM);
} else
break;
}
@ -4935,7 +4948,7 @@ static bool handle_cmd(enum action sel, const char *current, char *newpath)
setenv(env_cfg[NNNLVL], xitoa(r + 1), 1);
setenv(envs[ENV_NCUR], current, 1);
spawn(shell, NULL, NULL, F_CLI);
spawn(shell, NULL, NULL, NULL, F_CLI);
setenv(env_cfg[NNNLVL], xitoa(r), 1);
return TRUE;
}
@ -5423,7 +5436,7 @@ static void handle_openwith(const char *path, const char *name, char *newpath, c
(r == 'g' ? F_NOWAIT | F_NOTRACE | F_MULTI : 0));
if (r) {
mkpath(path, name, newpath);
spawn(tmp, newpath, NULL, r);
spawn(tmp, newpath, NULL, NULL, r);
}
}
@ -6346,7 +6359,7 @@ nochange:
&& strstr(g_buf, "text")
#endif
) {
spawn(editor, newpath, NULL, F_CLI);
spawn(editor, newpath, NULL, NULL, F_CLI);
if (cfg.filtermode) {
presel = FILTER;
clearfilter();
@ -6397,7 +6410,7 @@ nochange:
}
/* Invoke desktop opener as last resort */
spawn(opener, newpath, NULL, opener_flags);
spawn(opener, newpath, NULL, NULL, opener_flags);
/* Move cursor to the next entry if not the last entry */
if (g_state.autonext && cur != ndents - 1)
@ -6648,7 +6661,7 @@ nochange:
copycurname();
goto nochange;
case SEL_EDIT:
spawn(editor, newpath, NULL, F_CLI);
spawn(editor, newpath, NULL, NULL, F_CLI);
continue;
default: /* SEL_LOCK */
lock_terminal();
@ -6903,7 +6916,7 @@ nochange:
}
get_archive_cmd(newpath, tmp);
(r == 's') ? archive_selection(newpath, tmp, path)
: spawn(newpath, tmp, pdents[cur].name, F_CLI | F_CONFIRM);
: spawn(newpath, tmp, pdents[cur].name, NULL, F_CLI | F_CONFIRM);
mkpath(path, tmp, newpath);
if (access(newpath, F_OK) == 0) { /* File created */
@ -6965,7 +6978,7 @@ nochange:
if (sel == SEL_RENAME) {
/* Rename the file */
if (ret == 'd')
spawn("cp -rp", pdents[cur].name, tmp, F_SILENT);
spawn("cp -rp", pdents[cur].name, tmp, NULL, F_SILENT);
else if (renameat(fd, pdents[cur].name, fd, tmp) != 0) {
close(fd);
printwarn(&presel);
@ -8046,7 +8059,7 @@ int main(int argc, char *argv[])
}
#endif
if (g_state.pickraw || g_state.picker) {
if (g_state.picker) {
if (selbufpos) {
fd = g_state.pickraw ? STDOUT_FILENO : open(selpath, O_WRONLY | O_CREAT, 0600);
if ((fd == -1) || (seltofile(fd, NULL) != (size_t)(selbufpos)))