From a900b2c4fa928de32c30943029ccd4b67c4b7ee9 Mon Sep 17 00:00:00 2001 From: Arun Prakash Jana Date: Sun, 21 Apr 2019 23:59:51 +0530 Subject: [PATCH] Support config dir ~/.config/nnn --- README.md | 13 +- nnn.1 | 8 +- plugins/README.md | 2 +- plugins/kdeconnect | 8 +- plugins/ndiff | 2 +- scripts/clipboard-copier/copier | 10 +- src/nnn.c | 215 +++++++++++++++++++++++--------- 7 files changed, 171 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 98b02d9..1aa4e8e 100644 --- a/README.md +++ b/README.md @@ -320,11 +320,7 @@ Selected files are visually indicated by a `+`. The selection can now be listed, copied, moved, removed, archived or linked. -File paths are copied to the temporary file `DIR/.nnncp`, where `DIR` (by priority) is: - - $HOME or, - /tmp - $TMPDIR or, +File paths are copied to the temporary file `~/.config/nnn/.selection`. The path is shown in the help and configuration screen. @@ -379,7 +375,6 @@ The following indicators are used in the detail view: | `NNN_CONTEXT_COLORS='1234'` | specify per context color [default: '4444' (all blue)] | | `NNN_IDLE_TIMEOUT=300` | idle seconds before locking terminal [default: disabled] | | `NNN_COPIER='/absolute/path/to/copier'` | system clipboard copier script [default: none] | -| `NNN_PLUGIN_DIR=/home/user/nnn-plugins` | absolute path to plugins dir | | `NNN_NOTE=/home/user/Dropbox/notes` | path to note file [default: none] | | `NNN_TMPFILE=/tmp/nnn` | file to write current open dir path to for cd on quit | | `NNN_SSHFS_MNT_ROOT=/home/user/.netmnt` | absolute path to SSHFS mount point root | @@ -426,11 +421,9 @@ To lookup keyboard shortcuts at runtime, press ?. `nnn` can invoke plugins in the current directory (`$PWD` for the plugin) with the currently selected file name as the argument. -Copy the plugins of your interest from the [plugins](https://github.com/jarun/nnn/tree/master/plugins) directory and let `nnn` know the location: - - export NNN_PLUGIN_DIR=/absolute/path/to/plugins_dir +Copy the [plugins](https://github.com/jarun/nnn/tree/master/plugins) of your interest to `~/.config/nnn/plugins`. -Use the pick plugin shortcut to visit the plugin directory and pick a plugin. Repeating the same shortcut cancels the operation and puts you back in the original directory. +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. If you have an interesting plugin feel free to raise a PR. diff --git a/nnn.1 b/nnn.1 index f21ea14..cc28306 100644 --- a/nnn.1 +++ b/nnn.1 @@ -169,16 +169,10 @@ when dealing with the !, e and p commands respectively. A single combination to .Pp \fBNNN_COPIER:\fR system clipboard copier script. .Bd -literal - NOTE: File paths are copied to the tmp file \fBDIR/.nnncp\fR, where 'DIR' (by priority) is: - \fI$HOME\fR or, \fI$TMPDIR\fR or, \fI/tmp\fR. + NOTE: File paths are copied to the tmp file \fB~/.config/nnn/.selection\fR. The path is shown in the help and configuration screen. .Ed .Pp -\fBNNN_PLUGIN_DIR:\fR \fIabsolute\fR path to plugin directory. Selected plugin is invoked with currently selected file name as argument 1. -.Bd -literal - export NNN_PLUGIN_DIR=/absolute/path/to/plugins_dir -.Ed -.Pp \fBNNN_NOTE:\fR \fIabsolute\fR path to a note file. .Bd -literal export NNN_NOTE='/home/user/.mynotes' diff --git a/plugins/README.md b/plugins/README.md index 236ab00..1454103 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -24,7 +24,7 @@ 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 currently highlighted file (the file name is passed as the argument to a plugin) -- the current selection (by reading the file .nnncp, see the plugin `ndiff`) +- the current selection (by reading the file `~/.config/nnn/.selection`, see the plugin `ndiff`) Each script has a _Description_ section which provides more details on what the script does, if applicable. diff --git a/plugins/kdeconnect b/plugins/kdeconnect index 0fbafbb..a209f55 100755 --- a/plugins/kdeconnect +++ b/plugins/kdeconnect @@ -5,11 +5,13 @@ # Shell: POSIX compliant # Author: juacq97 +SELECTION=~/.config/nnn/.selection + id=$(kdeconnect-cli -a --id-only | awk '{print $1}') -if [ "$(find ~/.nnncp)" ]; then - kdeconnect-cli -d "$id" --share "$(cat ~/.nnncp)" +if [ "$(find "$SELECTION")" ]; then + kdeconnect-cli -d "$id" --share "$(cat "$SELECTION")" # If you want a system notification, uncomment the next 3 lines. -# notify-send -a "Kdeconnect" "Sending $(cat ~/.nnncp)" +# notify-send -a "Kdeconnect" "Sending $(cat "$SELECTION")" #else # notify-send -a "Kdeconnect" "No file selected" fi diff --git a/plugins/ndiff b/plugins/ndiff index e2f1125..fad28ad 100755 --- a/plugins/ndiff +++ b/plugins/ndiff @@ -5,4 +5,4 @@ # Shell: POSIX compliant # Author: Arun Prakash Jana -vimdiff $(cat ~/.nnncp | tr '\0' '\n') +vimdiff $(cat ~/.config/nnn/.selection | tr '\0' '\n') diff --git a/scripts/clipboard-copier/copier b/scripts/clipboard-copier/copier index 2aec820..e20dcf4 100755 --- a/scripts/clipboard-copier/copier +++ b/scripts/clipboard-copier/copier @@ -5,14 +5,16 @@ # Shell: POSIX compliant # Author: Arun Prakash Jana +SELECTION=~/.config/nnn/.selection + # Linux -cat ~/.nnncp | xargs -0 | xsel -bi +cat "$SELECTION" | xargs -0 | xsel -bi # macOS -# cat ~/.nnncp | xargs -0 | pbcopy +# cat "$SELECTION" | xargs -0 | pbcopy # Termux -# cat /data/data/com.termux/files/home/.nnncp | xargs -0 | termux-clipboard-set +# cat "$SELECTION" | xargs -0 | termux-clipboard-set # Cygwin -# cat ~/.nnncp | xargs -0 | clip +# cat "$SELECTION" | xargs -0 | clip diff --git a/src/nnn.c b/src/nnn.c index ff2ce82..91947b6 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -1,4 +1,5 @@ /* + * BSD 2-Clause License * * Copyright (C) 2014-2016, Lazaros Koromilas @@ -125,7 +126,7 @@ #define DESCRIPTOR_LEN 32 #define _ALIGNMENT 0x10 /* 16-byte alignment */ #define _ALIGNMENT_MASK 0xF -#define HOME_LEN_MAX 64 +#define TMP_LEN_MAX 64 #define CTX_MAX 4 #define DOT_FILTER_LEN 7 #define ASCII_MAX 128 @@ -279,6 +280,9 @@ static char *editor; static char *pager; static char *shell; static char *home; +static char *cfgdir; +static char *g_cppath; +static char *plugindir; static char *sshfsmnt; static blkcnt_t ent_blocks; static blkcnt_t dir_blocks; @@ -302,11 +306,8 @@ static sig_t oldsigtstp; /* For use in functions which are isolated and don't return the buffer */ static char g_buf[CMD_LEN_MAX] __attribute__ ((aligned)); -/* Buffer for file path copy file */ -static char g_cppath[PATH_MAX] __attribute__ ((aligned)); - -/* Buffer to store tmp file path */ -static char g_tmpfpath[HOME_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)); /* Replace-str for xargs on different platforms */ #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -386,19 +387,18 @@ static const char * const messages[] = { #define NNN_CONTEXT_COLORS 2 #define NNN_IDLE_TIMEOUT 3 #define NNN_COPIER 4 -#define NNN_PLUGIN_DIR 5 -#define NNN_NOTE 6 -#define NNN_TMPFILE 7 -#define NNN_SSHFS_MNT_ROOT 8 -#define NNNLVL 9 /* strings end here */ -#define NNN_USE_EDITOR 10 /* flags begin here */ -#define NNN_NO_AUTOSELECT 11 -#define NNN_RESTRICT_NAV_OPEN 12 -#define NNN_RESTRICT_0B 13 -#define NNN_OPENER_DETACH 14 -#define NNN_TRASH 15 +#define NNN_NOTE 5 +#define NNN_TMPFILE 6 +#define NNN_SSHFS_MNT_ROOT 7 +#define NNNLVL 8 /* strings end here */ +#define NNN_USE_EDITOR 9 /* flags begin here */ +#define NNN_NO_AUTOSELECT 10 +#define NNN_RESTRICT_NAV_OPEN 11 +#define NNN_RESTRICT_0B 12 +#define NNN_OPENER_DETACH 13 +#define NNN_TRASH 14 #ifdef __linux__ -#define NNN_OPS_PROG 16 +#define NNN_OPS_PROG 15 #endif static const char * const env_cfg[] = { @@ -407,7 +407,6 @@ static const char * const env_cfg[] = { "NNN_CONTEXT_COLORS", "NNN_IDLE_TIMEOUT", "NNN_COPIER", - "NNN_PLUGIN_DIR", "NNN_NOTE", "NNN_TMPFILE", "NNN_SSHFS_MNT_ROOT", @@ -566,7 +565,7 @@ static void printerr(int linenum) { exitcurses(); perror(xitoa(linenum)); - if (!cfg.picker && g_cppath[0]) + if (!cfg.picker && g_cppath) unlink(g_cppath); free(pcopybuf); exit(1); @@ -739,7 +738,7 @@ static char *xbasename(char *path) /* Writes buflen char(s) from buf to a file */ static void writecp(const char *buf, const size_t buflen) { - if (cfg.pickraw || !*g_cppath) + if (cfg.pickraw || !g_cppath) return; FILE *fp = fopen(g_cppath, "w"); @@ -799,7 +798,7 @@ static void showcplist(void) if (g_tmpfpath[0]) xstrlcpy(g_tmpfpath + g_tmpfplen - 1, messages[STR_TMPFILE], - HOME_LEN_MAX - g_tmpfplen); + TMP_LEN_MAX - g_tmpfplen); else { DPRINTF_S(messages[STR_NOHOME_ID]); return; @@ -821,9 +820,9 @@ static void showcplist(void) static bool cpsafe(void) { - /* Fail if copy file path not generated */ - if (!g_cppath[0]) { - printmsg("copy file not found"); + /* Fail if selection file path not generated */ + if (!g_cppath) { + printmsg("selection file not found"); return FALSE; } @@ -833,9 +832,9 @@ static bool cpsafe(void) return FALSE; } - /* Fail if copy file path isn't accessible */ + /* Fail if selection file path isn't accessible */ if (access(g_cppath, R_OK | W_OK) == -1) { - printmsg("check copyfile permission"); + printmsg("check selection file permission"); return FALSE; } @@ -2263,7 +2262,7 @@ static bool show_stats(const char *fpath, const char *fname, const struct stat * if (g_tmpfpath[0]) xstrlcpy(g_tmpfpath + g_tmpfplen - 1, messages[STR_TMPFILE], - HOME_LEN_MAX - g_tmpfplen); + TMP_LEN_MAX - g_tmpfplen); else return FALSE; @@ -2513,7 +2512,7 @@ static bool show_help(const char *path) if (g_tmpfpath[0]) xstrlcpy(g_tmpfpath + g_tmpfplen - 1, messages[STR_TMPFILE], - HOME_LEN_MAX - g_tmpfplen); + TMP_LEN_MAX - g_tmpfplen); else { printmsg(messages[STR_NOHOME_ID]); return FALSE; @@ -2557,8 +2556,8 @@ static bool show_help(const char *path) } } - if (g_cppath[0]) - dprintf(fd, "COPY FILE: %s\n", g_cppath); + if (g_cppath) + dprintf(fd, "SELECTION FILE: %s\n", g_cppath); dprintf(fd, "\nv%s\n%s\n", VERSION, GENERAL_INFO); close(fd); @@ -2713,7 +2712,7 @@ static int dentfill(char *path, struct entry **dents) dentp = *dents + n; - /* Copy file name */ + /* Selection file name */ dentp->name = (char *)((size_t)pnamebuf + off); dentp->nlen = xstrlcpy(dentp->name, namep, NAME_MAX + 1); off += dentp->nlen; @@ -2965,7 +2964,7 @@ static void browse(char *ipath) enum action sel; bool dir_changed = FALSE; struct stat sb; - char *path, *lastdir, *lastname, *dir, *tmp, *pluginpath = getenv(env_cfg[NNN_PLUGIN_DIR]); + char *path, *lastdir, *lastname, *dir, *tmp; atexit(dentfree); @@ -3110,9 +3109,9 @@ nochange: /* Handle plugin selection mode */ if (cfg.runplugin) { - if (!pluginpath || (cfg.runctx != cfg.curctx) + if (!plugindir || (cfg.runctx != cfg.curctx) /* Must be in plugin directory to select plugin */ - || (strcmp(path, pluginpath) != 0)) + || (strcmp(path, plugindir) != 0)) continue; mkpath(path, dents[cur].name, newpath); @@ -3534,7 +3533,7 @@ nochange: if (cfg.copymode) { /* - * Clear the tmp copy path file on first copy. + * Clear the selection file on first copy. * * This ensures that when the first file path is * copied into memory (but not written to tmp file @@ -3843,8 +3842,9 @@ nochange: /* Check if file creation failed */ if (r == -1) { - close(fd); printwarn(); + presel = MSGWAIT; + close(fd); goto nochange; } } @@ -3867,12 +3867,12 @@ nochange: spawn(shell, NULL, NULL, path, F_CLI); break; case SEL_PLUGIN: - if (!pluginpath) { - printwait("set NNN_PLUGIN_DIR", &presel); + if (!plugindir) { + printwait("plugins dir missing", &presel); goto nochange; } - if (stat(pluginpath, &sb) == -1) { + if (stat(plugindir, &sb) == -1) { printwarn(); presel = MSGWAIT; goto nochange; @@ -3888,7 +3888,7 @@ nochange: * If toggled, and still in the plugin dir, * switch to original directory */ - if (strcmp(path, pluginpath) == 0) { + if (strcmp(path, plugindir) == 0) { xstrlcpy(path, rundir, PATH_MAX); xstrlcpy(lastname, runfile, NAME_MAX); rundir[0] = runfile[0] = '\0'; @@ -3899,11 +3899,11 @@ nochange: } /* Check if directory is accessible */ - if (!xdiraccess(pluginpath)) + if (!xdiraccess(plugindir)) goto nochange; xstrlcpy(rundir, path, PATH_MAX); - xstrlcpy(path, pluginpath, PATH_MAX); + xstrlcpy(path, plugindir, PATH_MAX); if (ndents) xstrlcpy(runfile, dents[cur].name, NAME_MAX); cfg.runctx = cfg.curctx; @@ -4082,6 +4082,103 @@ static void usage(void) "v%s\n%s\n", __func__, VERSION, GENERAL_INFO); } +static bool create_dir(const char *path) +{ + if (!xdiraccess(path)) { + if (errno != ENOENT) + return FALSE; + + if (mkdir(path, 0777) == -1) + return FALSE; + } + + return TRUE; +} + +static bool setup_config(void) +{ + size_t r, len; + + /* Set up configuration file paths */ + len = strlen(home) + 1 + 20; /* add length of "/.config/nnn/plugins" */ + cfgdir = (char *)malloc(len); + plugindir = (char *)malloc(len); + if (!cfgdir || !plugindir) { + xerror(); + return FALSE; + } + r = xstrlcpy(cfgdir, home, len); + xstrlcpy(cfgdir + r - 1, "/.config/nnn", len - r); + DPRINTF_S(cfgdir); + + /* TODO: remove in next release */ + if (access(cfgdir, F_OK) == -1) + fprintf(stdout, "WARNING: selection file is ~/.config/nnn/.selection (see CHANGELOG)\n"); + + /* Create ~/.config/nnn */ + if (!create_dir(cfgdir)) { + xerror(); + return FALSE; + } + + xstrlcpy(cfgdir + r + 12 - 1, "/plugins", 9); + DPRINTF_S(cfgdir); + + xstrlcpy(plugindir, cfgdir, len); + DPRINTF_S(plugindir); + + /* Create ~/.config/nnn/plugins */ + if (!create_dir(cfgdir)) { + xerror(); + return FALSE; + } + + /* Reset to config path */ + cfgdir[r + 11] = '\0'; + DPRINTF_S(cfgdir); + + /* Set selection file path */ + if (!cfg.picker) { + /* Length of "/.config/nnn/.selection" */ + g_cppath = (char *)malloc(len + 3); + r = xstrlcpy(g_cppath, cfgdir, len + 3); + xstrlcpy(g_cppath + r - 1, "/.selection", 12); + DPRINTF_S(g_cppath); + } + + return TRUE; +} + +static bool set_tmp_path() +{ + char *path; + + if (xdiraccess("/tmp")) + g_tmpfplen = xstrlcpy(g_tmpfpath, "/tmp", TMP_LEN_MAX); + else { + path = getenv("TMPDIR"); + if (path) + g_tmpfplen = xstrlcpy(g_tmpfpath, path, TMP_LEN_MAX); + else { + fprintf(stderr, "set TMPDIR\n"); + return FALSE; + } + } + + return TRUE; +} + +static void cleanup(void) +{ + free(g_cppath); + free(plugindir); + free(cfgdir); + +#ifdef DBGMODE + disabledbg(); +#endif +} + int main(int argc, char *argv[]) { char cwd[PATH_MAX] __attribute__ ((aligned)); @@ -4121,7 +4218,7 @@ int main(int argc, char *argv[]) else { /* copier used as tmp var */ copier = realpath(optarg, g_cppath); - if (!g_cppath[0]) { + if (!g_cppath) { xerror(); return 1; } @@ -4170,12 +4267,20 @@ int main(int argc, char *argv[]) #ifdef DBGMODE enabledbg(); - atexit(disabledbg); #endif + atexit(cleanup); + home = getenv("HOME"); + if (!home) { + fprintf(stderr, "set HOME\n"); + return 1; + } DPRINTF_S(home); + if (!setup_config()) + return 1; + /* Get custom opener, if set */ opener = xgetenv(env_cfg[NNN_OPENER], utils[OPENER]); if (getenv(env_cfg[NNN_OPENER_DETACH])) @@ -4276,21 +4381,9 @@ int main(int argc, char *argv[]) if (getenv(env_cfg[NNN_TRASH])) cfg.trash = 1; - /* Prefix for other temporary ops */ - if (home) - g_tmpfplen = xstrlcpy(g_tmpfpath, home, HOME_LEN_MAX); - else if (xdiraccess("/tmp")) - g_tmpfplen = xstrlcpy(g_tmpfpath, "/tmp", HOME_LEN_MAX); - else { - copier = getenv("TMPDIR"); - if (copier) - g_tmpfplen = xstrlcpy(g_tmpfpath, copier, HOME_LEN_MAX); - } - - if (!cfg.picker && g_tmpfplen) { - xstrlcpy(g_cppath, g_tmpfpath, PATH_MAX); - xstrlcpy(g_cppath + g_tmpfplen - 1, "/.nnncp", PATH_MAX - g_tmpfplen); - } + /* Prefix for temporary files */ + if (!set_tmp_path()) + return 1; /* Get SSHFS mountpoint */ sshfsmnt = getenv("NNN_SSHFS_MNT_ROOT"); @@ -4365,7 +4458,7 @@ int main(int argc, char *argv[]) if (opt != (int)(copybufpos)) xerror(); } - } else if (!cfg.picker && g_cppath[0]) + } else if (!cfg.picker && g_cppath) unlink(g_cppath); /* Free the copy buffer */