mirror of
https://github.com/jarun/nnn.git
synced 2024-11-28 05:41:31 +00:00
Implement plugins control of nnn + plugins (#364)
* Implement plugins control of nnn + plugins * Refactor plugins control code and fix getplugs to recognize hidden files * Fix bug when going to dir on non-current context from plugin * Fix some plugins to work on openbsd and freebsd * Renamings * Switch to -R flag in cp instead of -r; BSDs complain * Change braces of function location * Rewrite plugin creation in README and add new plugins to the table * Update the fzcd script to include fzy or fzf * Change plugin name resolve-link-dir -> lncd * Fixing plugins README table * Remove some cd plugins but add them as examples to plugins README
This commit is contained in:
parent
0144f44060
commit
9afd7cf3bf
33
plugins/.nnn-plugin-helper
Normal file
33
plugins/.nnn-plugin-helper
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Helper script for plugins
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad
|
||||||
|
|
||||||
|
selection=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/.selection
|
||||||
|
|
||||||
|
## Ask nnn to switch to directory $1 in context $2.
|
||||||
|
## If $2 is not provided, the function asks explicitly.
|
||||||
|
nnn_cd () {
|
||||||
|
dir=$1
|
||||||
|
|
||||||
|
if [ -z "$NNN_PIPE" ]; then
|
||||||
|
echo "No pipe file found" 1>&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
context=$2
|
||||||
|
else
|
||||||
|
echo -n "Choose context 1-4 (blank for current): "
|
||||||
|
read context
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -n ${context:-0}$dir > $NNN_PIPE
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_exists () {
|
||||||
|
which "$1" > /dev/null 2>&1
|
||||||
|
echo $?
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ The currently available plugins are listed below.
|
||||||
| checksum | sh | md5sum,<br>sha256sum | Create and verify checksums |
|
| checksum | sh | md5sum,<br>sha256sum | Create and verify checksums |
|
||||||
| drag-file | sh | [dragon](https://github.com/mwh/dragon) | Drag and drop files from nnn |
|
| drag-file | sh | [dragon](https://github.com/mwh/dragon) | Drag and drop files from nnn |
|
||||||
| drop-file | sh | [dragon](https://github.com/mwh/dragon) | Drag and drop files into nnn |
|
| drop-file | sh | [dragon](https://github.com/mwh/dragon) | Drag and drop files into nnn |
|
||||||
|
| fzcd | sh | fzy/fzf<br>(optional fd) | Change to the directory of a file/directory selected by fzy/fzf |
|
||||||
| fzy-open | sh | fzy, xdg-open | Fuzzy find a file in dir subtree and edit or xdg-open |
|
| fzy-open | sh | fzy, xdg-open | Fuzzy find a file in dir subtree and edit or xdg-open |
|
||||||
| getplugs | sh | curl | Update plugins |
|
| getplugs | sh | curl | Update plugins |
|
||||||
| gutenread | sh | curl, unzip, w3m<br>[epr](https://github.com/wustho/epr) (optional)| Browse, download, read from Project Gutenberg |
|
| gutenread | sh | curl, unzip, w3m<br>[epr](https://github.com/wustho/epr) (optional)| Browse, download, read from Project Gutenberg |
|
||||||
|
@ -67,28 +68,68 @@ With this, plugin `fzy-open` can be run with the keybind <kbd>:o</kbd>, `mocplay
|
||||||
|
|
||||||
**Method 2:** Use the _pick plugin_ shortcut to visit the plugin directory and execute a plugin. Repeating the same shortcut cancels the operation and puts you back in the original directory.
|
**Method 2:** Use the _pick plugin_ shortcut to visit the plugin directory and execute a plugin. Repeating the same shortcut cancels the operation and puts you back in the original directory.
|
||||||
|
|
||||||
## File access from plugins
|
## Create your own plugins
|
||||||
|
|
||||||
Plugins can access:
|
Plugins are a powerful yet easy way to extend the capabilities of `nnn`.
|
||||||
- all files in the directory (`nnn` switches to the dir where the plugin is to be run so the dir is `$PWD` for the plugin)
|
|
||||||
- the current file under the cursor (the file name is passed as the first argument to a plugin)
|
Plugins are scripts that can be written in any scripting language. However, POSIX-compliant shell scripts runnable in `sh` are preferred.
|
||||||
- the traversed path where plugin is invoked (this is the second argument to the plugin; for all practical purposes this is the same as `$PWD` except paths with symlinks)
|
|
||||||
- the current selection (by reading the file `.selection` in config dir, see the plugin `ndiff`)
|
|
||||||
|
|
||||||
Each script has a _Description_ section which provides more details on what the script does, if applicable.
|
Each script has a _Description_ section which provides more details on what the script does, if applicable.
|
||||||
|
|
||||||
## Create your own plugins
|
The plugins reside in `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`.
|
||||||
|
|
||||||
Plugins are scripts and all scripting languages should work. However, POSIX-compliant shell scripts runnable in `sh` are preferred. If that's too rudimentary for your use case, use Python, Perl or Ruby.
|
When `nnn` executes a plugin, it does the following:
|
||||||
|
- Change to the directory where the plugin is to be run (`$PWD` pointing to the active directory)
|
||||||
|
- Passes two 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)
|
||||||
|
- Sets the environment variable `NNN_PIPE` used to control `nnn` active directory.
|
||||||
|
|
||||||
You can create your own plugins by putting them in `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`.
|
Plugins can also access the current selections by reading the `.selections` file in the config directory (See the `ndiff` plugin for example).
|
||||||
|
|
||||||
For example, you could create a executable shell script `git-changes`:
|
#### Controlling `nnn`'s active directory
|
||||||
|
`nnn` provides a mechanism for plugins to control its active directory.
|
||||||
|
The way to do so is by writing to the pipe pointed by the environment variable `NNN_PIPE`.
|
||||||
|
The plugin should write a single string in the format `<number><path>` without a newline at the end. For example, `1/etc`.
|
||||||
|
The number indicates the context to change the active directory of (0 is used to indicate the current context).
|
||||||
|
|
||||||
|
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.
|
||||||
|
Usage examples can be found in the Examples section below.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
There are many plugins provided by `nnn` which can be used as examples. Here are a few simple selected examples.
|
||||||
|
|
||||||
|
- Show the git log of changes to the particular file along with the code for a quick and easy review.
|
||||||
|
```sh
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
git log -p -- "$@"
|
git log -p -- "$1"
|
||||||
|
```
|
||||||
|
|
||||||
And then trigger it by hitting the pick plugin key and selecting `git-changes` which will conveniently show the git log of changes to the particular file along with the code for a quick and easy review.
|
- Change to directory in clipboard using helper script
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. $(dirname $0/.nnn-plugin-helper)
|
||||||
|
|
||||||
|
nnn_cd "$(xsel -ob)"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Change direcory to the location of a link using helper script with specific context (current)
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. $(dirname $0/.nnn-plugin-helper)
|
||||||
|
|
||||||
|
nnn_cd "$(dirname $(readlink -fn $1))" 0
|
||||||
|
```
|
||||||
|
|
||||||
|
- Change to arbitrary directory without helper script
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
echo -n "cd to: "
|
||||||
|
read dir
|
||||||
|
|
||||||
|
echo -n "0$dir" > $NNN_PIPE
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing plugins
|
## Contributing plugins
|
||||||
|
|
||||||
|
|
32
plugins/fzcd
Executable file
32
plugins/fzcd
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
# Description: Run fzf and go to the directory of the file selected
|
||||||
|
#
|
||||||
|
# Shell: POSIX compliant
|
||||||
|
# Author: Anna Arad
|
||||||
|
|
||||||
|
. $(dirname $0)/.nnn-plugin-helper
|
||||||
|
|
||||||
|
if [ "$(cmd_exists fzy)" -eq "0" ]; then
|
||||||
|
if [ "$(cmd_exists fd)" -eq "0" ]; then
|
||||||
|
fd=fd
|
||||||
|
elif [ "$(cmd_exists fdfind)" -eq "0" ]; then
|
||||||
|
fd=fdfind
|
||||||
|
else
|
||||||
|
fd=find
|
||||||
|
fi
|
||||||
|
|
||||||
|
sel=$($fd | fzy)
|
||||||
|
elif [ "$(cmd_exists fzf)" -eq "0" ]; then
|
||||||
|
sel=$(fzf --print0)
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$?" -eq "0" ]; then
|
||||||
|
case "$(file -bi "$sel")" in
|
||||||
|
*directory*) ;;
|
||||||
|
*) sel=$(dirname $sel) ;;
|
||||||
|
esac
|
||||||
|
nnn_cd "$PWD/$sel"
|
||||||
|
fi
|
|
@ -8,15 +8,27 @@
|
||||||
CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/
|
CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/
|
||||||
PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins
|
PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins
|
||||||
|
|
||||||
# backup any earlier plugins
|
is_cmd_exists () {
|
||||||
if [ -d $PLUGIN_DIR ]; then
|
which "$1" > /dev/null 2>&1
|
||||||
tar -C $CONFIG_DIR -cf $CONFIG_DIR"plugins-$(date '+%Y%m%d%H%M').tar.bz2" plugins/
|
echo $?
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$(is_cmd_exists sudo)" == "0" ]; then
|
||||||
|
sucmd=sudo
|
||||||
|
elif [ "$(is_cmd_exists doas)" == "0" ]; then
|
||||||
|
sucmd=doas
|
||||||
|
else
|
||||||
|
sucmd=: # noop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p $PLUGIN_DIR
|
# backup any earlier plugins
|
||||||
cd $PLUGIN_DIR
|
if [ -d $PLUGIN_DIR ]; then
|
||||||
|
tar -C $CONFIG_DIR -czf $CONFIG_DIR"plugins-$(date '+%Y%m%d%H%M').tar.gz" plugins/
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $CONFIG_DIR
|
||||||
curl -Ls -O https://github.com/jarun/nnn/archive/master.tar.gz
|
curl -Ls -O https://github.com/jarun/nnn/archive/master.tar.gz
|
||||||
tar -xf master.tar.gz
|
tar -zxf master.tar.gz
|
||||||
cp -vf nnn-master/plugins/* .
|
cp -vRf nnn-master/plugins .
|
||||||
sudo mv -vf nnn-master/misc/nlaunch/nlaunch /usr/local/bin/
|
$sucmd mv -vf nnn-master/misc/nlaunch/nlaunch /usr/local/bin/
|
||||||
rm -rf nnn-master/ master.tar.gz README.md
|
rm -rf nnn-master/ master.tar.gz README.md
|
||||||
|
|
78
src/nnn.c
78
src/nnn.c
|
@ -342,6 +342,11 @@ static char g_buf[CMD_LEN_MAX] __attribute__ ((aligned));
|
||||||
/* Buffer to store tmp file path to show selection, file stats and help */
|
/* Buffer to store tmp file path to show selection, file stats and help */
|
||||||
static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned));
|
static char g_tmpfpath[TMP_LEN_MAX] __attribute__ ((aligned));
|
||||||
|
|
||||||
|
/* Buffer to store plugins control pipe location */
|
||||||
|
static char g_pipepath[TMP_LEN_MAX] __attribute__ ((aligned));
|
||||||
|
|
||||||
|
static bool g_plinit = FALSE;
|
||||||
|
|
||||||
/* Replace-str for xargs on different platforms */
|
/* Replace-str for xargs on different platforms */
|
||||||
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
|
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
|
||||||
#define REPLACE_STR 'J'
|
#define REPLACE_STR 'J'
|
||||||
|
@ -429,9 +434,10 @@ static const char * const messages[] = {
|
||||||
#define NNN_CONTEXT_COLORS 2
|
#define NNN_CONTEXT_COLORS 2
|
||||||
#define NNN_IDLE_TIMEOUT 3
|
#define NNN_IDLE_TIMEOUT 3
|
||||||
#define NNN_COPIER 4
|
#define NNN_COPIER 4
|
||||||
#define NNNLVL 5 /* strings end here */
|
#define NNNLVL 5
|
||||||
#define NNN_USE_EDITOR 6 /* flags begin here */
|
#define NNN_PIPE 6 /* strings end here */
|
||||||
#define NNN_TRASH 7
|
#define NNN_USE_EDITOR 7 /* flags begin here */
|
||||||
|
#define NNN_TRASH 8
|
||||||
|
|
||||||
static const char * const env_cfg[] = {
|
static const char * const env_cfg[] = {
|
||||||
"NNN_BMS",
|
"NNN_BMS",
|
||||||
|
@ -440,6 +446,7 @@ static const char * const env_cfg[] = {
|
||||||
"NNN_IDLE_TIMEOUT",
|
"NNN_IDLE_TIMEOUT",
|
||||||
"NNN_COPIER",
|
"NNN_COPIER",
|
||||||
"NNNLVL",
|
"NNNLVL",
|
||||||
|
"NNN_PIPE",
|
||||||
"NNN_USE_EDITOR",
|
"NNN_USE_EDITOR",
|
||||||
"NNN_TRASH",
|
"NNN_TRASH",
|
||||||
};
|
};
|
||||||
|
@ -499,7 +506,7 @@ static int spawn(char *file, char *arg1, char *arg2, const char *dir, uchar flag
|
||||||
static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
|
||||||
static int dentfind(const char *fname, int n);
|
static int dentfind(const char *fname, int n);
|
||||||
static void move_cursor(int target, int ignore_scrolloff);
|
static void move_cursor(int target, int ignore_scrolloff);
|
||||||
static bool getutil(char *util);
|
static inline bool getutil(char *util);
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
|
||||||
|
@ -3340,24 +3347,68 @@ static void show_help(const char *path)
|
||||||
unlink(g_tmpfpath);
|
unlink(g_tmpfpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool run_selected_plugin(char *path, const char *file, char *newpath, char *rundir, char *runfile, char *lastname)
|
static bool plctrl_init()
|
||||||
{
|
{
|
||||||
|
snprintf(g_buf, CMD_LEN_MAX, "nnn-pipe.%d", getpid());
|
||||||
|
mkpath(g_tmpfpath, g_buf, g_pipepath);
|
||||||
|
unlink(g_pipepath);
|
||||||
|
if (mkfifo(g_pipepath, 0600) != 0)
|
||||||
|
return _FAILURE;
|
||||||
|
|
||||||
|
setenv(env_cfg[NNN_PIPE], g_pipepath, TRUE);
|
||||||
|
|
||||||
|
return _SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool run_selected_plugin(char **path, const char *file, char *newpath, char *rundir, char *runfile, char **lastname, char **lastdir)
|
||||||
|
{
|
||||||
|
if (!g_plinit) {
|
||||||
|
plctrl_init();
|
||||||
|
g_plinit = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
if ((cfg.runctx != cfg.curctx)
|
if ((cfg.runctx != cfg.curctx)
|
||||||
/* Must be in plugin directory to select plugin */
|
/* Must be in plugin directory to select plugin */
|
||||||
|| (strcmp(path, plugindir) != 0))
|
|| (strcmp(*path, plugindir) != 0))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
mkpath(path, file, newpath);
|
int fd = open(g_pipepath, O_RDONLY | O_NONBLOCK);
|
||||||
|
if (fd == -1)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
mkpath(*path, file, newpath);
|
||||||
/* Copy to path so we can return back to earlier dir */
|
/* Copy to path so we can return back to earlier dir */
|
||||||
xstrlcpy(path, rundir, PATH_MAX);
|
xstrlcpy(*path, rundir, PATH_MAX);
|
||||||
if (runfile[0]) {
|
if (runfile[0]) {
|
||||||
xstrlcpy(lastname, runfile, NAME_MAX);
|
xstrlcpy(*lastname, runfile, NAME_MAX);
|
||||||
spawn(newpath, lastname, path, path, F_NORMAL);
|
spawn(newpath, *lastname, *path, *path, F_NORMAL);
|
||||||
runfile[0] = '\0';
|
runfile[0] = '\0';
|
||||||
} else
|
} else
|
||||||
spawn(newpath, NULL, path, path, F_NORMAL);
|
spawn(newpath, NULL, *path, *path, F_NORMAL);
|
||||||
rundir[0] = '\0';
|
rundir[0] = '\0';
|
||||||
cfg.runplugin = 0;
|
cfg.runplugin = 0;
|
||||||
|
|
||||||
|
size_t len = read(fd, g_buf, PATH_MAX);
|
||||||
|
g_buf[len] = '\0';
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (len > 1) {
|
||||||
|
int ctx = g_buf[0] - '0';
|
||||||
|
|
||||||
|
if (ctx == 0) {
|
||||||
|
xstrlcpy(*lastdir, *path, PATH_MAX);
|
||||||
|
xstrlcpy(*path, g_buf + 1, PATH_MAX);
|
||||||
|
} else if (ctx >= 1 && ctx <= CTX_MAX) {
|
||||||
|
int r = ctx - 1;
|
||||||
|
g_ctx[r].c_cfg.ctxactive = 0;
|
||||||
|
savecurctx(&cfg, g_buf + 1, dents[cur].name, r);
|
||||||
|
*path = g_ctx[r].c_path;
|
||||||
|
*lastdir = g_ctx[r].c_last;
|
||||||
|
*lastname = g_ctx[r].c_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4146,8 +4197,9 @@ nochange:
|
||||||
|
|
||||||
/* Handle plugin selection mode */
|
/* Handle plugin selection mode */
|
||||||
if (cfg.runplugin) {
|
if (cfg.runplugin) {
|
||||||
if (!run_selected_plugin(path, dents[cur].name, newpath, rundir, runfile, lastname))
|
if (!run_selected_plugin(&path, dents[cur].name, newpath, rundir, runfile, &lastname, &lastdir))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
setdirwatch();
|
setdirwatch();
|
||||||
goto begin;
|
goto begin;
|
||||||
}
|
}
|
||||||
|
@ -5244,6 +5296,8 @@ static void cleanup(void)
|
||||||
free(bmstr);
|
free(bmstr);
|
||||||
free(pluginstr);
|
free(pluginstr);
|
||||||
|
|
||||||
|
unlink(g_pipepath);
|
||||||
|
|
||||||
#ifdef DBGMODE
|
#ifdef DBGMODE
|
||||||
disabledbg();
|
disabledbg();
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue