From 462531b8c7290d1e2c556127d615732d5a944b3a Mon Sep 17 00:00:00 2001 From: Arun Prakash Jana Date: Fri, 28 May 2021 19:04:14 +0530 Subject: [PATCH] Plugin fzdirs: fuzzy search multiple directories --- plugins/README.md | 20 ++++++++------ plugins/fzcd | 2 +- plugins/fzdirs | 70 +++++++++++++++++++++++++++++++++++++++++++++++ src/nnn.c | 20 ++++++++------ 4 files changed, 94 insertions(+), 18 deletions(-) create mode 100755 plugins/fzdirs diff --git a/plugins/README.md b/plugins/README.md index 2ab729de..4cd176bc 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -26,6 +26,7 @@ Plugins extend the capabilities of `nnn`. They are _executable_ scripts (or bina | [finder](finder) | Run custom find command and list | sh | - | | [fixname](fixname) | Clean filename to be more shell-friendly [✓] | bash | sed | | [fzcd](fzcd) | Change to the directory of a fuzzy-selected file/dir | sh | fzf | +| [fzdirs](fzdirs) | Fuzzy search multiple directories [✓] | sh | fzf, fd | | [fzhist](fzhist) | Fuzzy-select a cmd from history, edit in `$EDITOR` and run | sh | fzf, mktemp | | [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 | @@ -189,9 +190,9 @@ 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 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. + 1. `$1`: The hovered file's name. + 2. `$2`: The working directory (might differ from `$PWD` in case of symlinked paths; non-canonical). + 3. `$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. @@ -200,21 +201,24 @@ Plugins can also read the `.selection` file in the config directory. Plugins can be written in any scripting language. However, POSIX-compliant shell scripts runnable in `sh` are preferred. -Drop the plugin in `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins` and make it executable. Optionally add a hotkey in `$NNN_PLUG` for frequent usage. +Make the file executable and drop it in the plugin install directory. Optionally add a hotkey in `$NNN_PLUG` for frequent usage. #### Send data to `nnn` `nnn` provides a mechanism for plugins to send data to `nnn` to control its active directory or invoke the list mode. 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 `` without a newline at the end. For example, `1c/etc`. +The plugin should write a single string in the format `(<->)` without a newline at the end. For example, `1c/etc`. + +The optional `-` at the **beginning of the stream** instructs `nnn` to clear the selection. +In cases where the data transfer to `nnn` has to happen while the selection file is being read (e.g. in a loop), the plugin should +create a tmp copy of the selection file, inform `nnn` to clear the selection and then do the subsequent processing with the tmp file. The `ctxcode` indicates the context to change the active directory of. | Context code | Meaning | |:---:| --- | -| `1`-`4` | context number | -| `0` | current context | | `+` | smart context (next inactive else current) | -| `-` | clear the selection | +| `0` | current context | +| `1`-`4` | context number | The `opcode` indicates the operation type. diff --git a/plugins/fzcd b/plugins/fzcd index 907f4f94..537e50e8 100755 --- a/plugins/fzcd +++ b/plugins/fzcd @@ -9,7 +9,7 @@ if [ "$(cmd_exists fzf)" -eq "0" ]; then sel=$(fzf) - # Show only the file ane parent dir + # Show only the file and parent dir # sel=$(fzf --delimiter / --with-nth=-2,-1 --tiebreak=begin --info=hidden) else exit 1 diff --git a/plugins/fzdirs b/plugins/fzdirs new file mode 100755 index 00000000..57748a6a --- /dev/null +++ b/plugins/fzdirs @@ -0,0 +1,70 @@ +#!/usr/bin/env sh + +# Description: Fuzzy search multiple locations read-in from a path-list +# file and open the selected file's directory in a smart context. +# Dependencies: fzf, fd +# +# Details: Paths in list file should be newline-separated absolute paths. +# Paths can be file paths; the script will scan the parent dirs. +# +# The path-list file can be generated easily: +# - pick the (file)paths in picker mode to path-list file +# - OR, edit selection in nnn and save as path-list file +# +# The plugin clears nnn selection as the user can be tempted to delete +# duplicate files after finding copies and remove selection by mistake. +# +# Shell: POSIX compliant +# Author: Arun Prakash Jana + +IFS="$(printf '\n\r')" + +. "$(dirname "$0")"/.nnn-plugin-helper + +CTX=+ + +if [ "$(cmd_exists fzf)" -eq "0" ] && [ -s "$1" ]; then + + tmpfile=$(mktemp /tmp/abc-script.XXXXXX) + + for entry in $(tr '\0' '\n' < "$1") + do + if [ -d "$entry" ]; then + printf "%s\n" "$entry" >> "$tmpfile" + elif [ -f "$entry" ]; then + printf "%s\n" "$(dirname "$entry")" >> "$tmpfile" + fi + done + + # Clear selection + if [ -p "$NNN_PIPE" ]; then + printf "-" >"$NNN_PIPE" + fi + + sel=$(xargs -d '\n' -a "$tmpfile" fd -H . | fzf --delimiter / --tiebreak=begin --info=hidden) + + rm "$tmpfile" +else + exit 1 +fi + +if [ -n "$sel" ]; then + if [ "$sel" = "." ] || { ! [ -d "$sel" ] && ! [ -f "$sel" ]; }; then + exit 0 + fi + + # Check if selected path returned + # by fzf command is absolute + case $sel in + /*) nnn_cd "$sel" "$CTX" ;; + *) + # Remove "./" prefix if it exists + sel="${sel#./}" + + if [ "$PWD" = "/" ]; then + nnn_cd "/$sel" "$CTX" + else + nnn_cd "$PWD/$sel" "$CTX" + fi;; + esac +fi diff --git a/src/nnn.c b/src/nnn.c index e13750e1..6faca85d 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -4758,17 +4758,19 @@ static void readpipe(int fd, char **path, char **lastname, char **lastdir) { int r; char ctx, *nextpath = NULL; - ssize_t len = read_nointr(fd, g_buf, 1); - if (len != 1) + if (read_nointr(fd, g_buf, 1) != 1) return; + if (g_buf[0] == '-') { /* Clear selection on '-' */ + clearselection(); + if (read_nointr(fd, g_buf, 1) != 1) + return; + } + if (g_buf[0] == '+') ctx = (char)(get_free_ctx() + 1); - else if (g_buf[0] == '-') { /* Clear selection on '-' */ - clearselection(); - return; - } else if (g_buf[0] < '0') + else if (g_buf[0] < '0') return; else { ctx = g_buf[0] - '0'; @@ -4776,14 +4778,14 @@ static void readpipe(int fd, char **path, char **lastname, char **lastdir) return; } - len = read_nointr(fd, g_buf, 1); - if (len != 1) + if (read_nointr(fd, g_buf, 1) != 1) return; char op = g_buf[0]; if (op == 'c') { - len = read_nointr(fd, g_buf, PATH_MAX); + ssize_t len = read_nointr(fd, g_buf, PATH_MAX); + if (len <= 0) return;