]> Sergey Matveev's repositories - nnn.git/commitdiff
Implement plugins control of nnn + plugins (#364)
authorAnna Arad <4895022+annagrram@users.noreply.github.com>
Wed, 23 Oct 2019 10:04:12 +0000 (13:04 +0300)
committerMischievous Meerkat <engineerarun@gmail.com>
Wed, 23 Oct 2019 10:04:12 +0000 (15:34 +0530)
* 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

plugins/.nnn-plugin-helper [new file with mode: 0644]
plugins/README.md
plugins/fzcd [new file with mode: 0755]
plugins/getplugs
src/nnn.c

diff --git a/plugins/.nnn-plugin-helper b/plugins/.nnn-plugin-helper
new file mode 100644 (file)
index 0000000..ec66a29
--- /dev/null
@@ -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 $?
+}
index 85550ca08509dffa283bf35214c8637800910b84..9d48ec30385299249db007ed9e1e467a56e0b610 100644 (file)
@@ -14,6 +14,7 @@ The currently available plugins are listed below.
 | checksum | sh | md5sum,<br>sha256sum | Create and verify checksums |
 | 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 |
+| 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 |
 | getplugs | sh | curl | Update plugins |
 | 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.
 
-## File access from plugins
+## Create your own plugins
+
+Plugins are a powerful yet easy way to extend the capabilities of `nnn`.
 
-Plugins can access:
-- 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)
-- 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`)
+Plugins are scripts that can be written in any scripting language. However, POSIX-compliant shell scripts runnable in `sh` are preferred.
 
 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`.
+
+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.
+
+Plugins can also access the current selections by reading the `.selections` file in the config directory (See the `ndiff` plugin for example).
 
-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.
+#### 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).
 
-You can create your own plugins by putting them in `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`.
+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.
 
-For example, you could create a executable shell script `git-changes`:
+#### 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
-    git log -p -- "$@"
+    git log -p -- "$1"
+    ```
+    
+- Change to directory in clipboard using helper script
+    ```sh
+    #!/usr/bin/env sh
+    . $(dirname $0/.nnn-plugin-helper)
+
+    nnn_cd "$(xsel -ob)"
+    ```
 
-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 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
 
diff --git a/plugins/fzcd b/plugins/fzcd
new file mode 100755 (executable)
index 0000000..443ac2d
--- /dev/null
@@ -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
index e144be39c075257ef5d61086ebd6ebe00da478cc..42d5400d45d37c9e055d201fe06147ded6553131 100755 (executable)
@@ -8,15 +8,27 @@
 CONFIG_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/
 PLUGIN_DIR=${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins
 
+is_cmd_exists () {
+    which "$1" > /dev/null 2>&1
+    echo $?
+}
+
+if [ "$(is_cmd_exists sudo)" == "0" ]; then
+    sucmd=sudo
+elif [ "$(is_cmd_exists doas)" == "0" ]; then
+    sucmd=doas
+else
+    sucmd=: # noop
+fi
+
 # backup any earlier plugins
 if [ -d $PLUGIN_DIR ]; then
-    tar -C $CONFIG_DIR -cf $CONFIG_DIR"plugins-$(date '+%Y%m%d%H%M').tar.bz2" plugins/
+    tar -C $CONFIG_DIR -czf $CONFIG_DIR"plugins-$(date '+%Y%m%d%H%M').tar.gz" plugins/
 fi
 
-mkdir -p $PLUGIN_DIR
-cd $PLUGIN_DIR
+cd $CONFIG_DIR
 curl -Ls -O https://github.com/jarun/nnn/archive/master.tar.gz
-tar -xf master.tar.gz
-cp -vf nnn-master/plugins/* .
-sudo mv -vf nnn-master/misc/nlaunch/nlaunch /usr/local/bin/
+tar -zxf master.tar.gz
+cp -vRf nnn-master/plugins .
+$sucmd mv -vf nnn-master/misc/nlaunch/nlaunch /usr/local/bin/
 rm -rf nnn-master/ master.tar.gz README.md
index da0a64755b099e40ee6559e3c0a9bdde7ca50f38..2b506470a6c921de8eeceb620666ddb6b55b0b4e 100644 (file)
--- a/src/nnn.c
+++ b/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 */
 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 */
 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
 #define REPLACE_STR 'J'
@@ -429,9 +434,10 @@ static const char * const messages[] = {
 #define NNN_CONTEXT_COLORS 2
 #define NNN_IDLE_TIMEOUT 3
 #define NNN_COPIER 4
-#define NNNLVL 5 /* strings end here */
-#define NNN_USE_EDITOR 6 /* flags begin here */
-#define NNN_TRASH 7
+#define NNNLVL 5
+#define NNN_PIPE 6 /* strings end here */
+#define NNN_USE_EDITOR 7 /* flags begin here */
+#define NNN_TRASH 8
 
 static const char * const env_cfg[] = {
        "NNN_BMS",
@@ -440,6 +446,7 @@ static const char * const env_cfg[] = {
        "NNN_IDLE_TIMEOUT",
        "NNN_COPIER",
        "NNNLVL",
+       "NNN_PIPE",
        "NNN_USE_EDITOR",
        "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 dentfind(const char *fname, int n);
 static void move_cursor(int target, int ignore_scrolloff);
-static bool getutil(char *util);
+static inline bool getutil(char *util);
 
 /* Functions */
 
@@ -3340,24 +3347,68 @@ static void show_help(const char *path)
        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)
            /* Must be in plugin directory to select plugin */
-           || (strcmp(path, plugindir) != 0))
+           || (strcmp(*path, plugindir) != 0))
                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 */
-       xstrlcpy(path, rundir, PATH_MAX);
+       xstrlcpy(*path, rundir, PATH_MAX);
        if (runfile[0]) {
-               xstrlcpy(lastname, runfile, NAME_MAX);
-               spawn(newpath, lastname, path, path, F_NORMAL);
+               xstrlcpy(*lastname, runfile, NAME_MAX);
+               spawn(newpath, *lastname, *path, *path, F_NORMAL);
                runfile[0] = '\0';
        } else
-               spawn(newpath, NULL, path, path, F_NORMAL);
+               spawn(newpath, NULL, *path, *path, F_NORMAL);
        rundir[0] = '\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;
 }
 
@@ -4146,8 +4197,9 @@ nochange:
 
                                /* Handle plugin selection mode */
                                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;
+
                                        setdirwatch();
                                        goto begin;
                                }
@@ -5244,6 +5296,8 @@ static void cleanup(void)
        free(bmstr);
        free(pluginstr);
 
+       unlink(g_pipepath);
+
 #ifdef DBGMODE
        disabledbg();
 #endif