From f90c37cd5e1860e6fd29aa7a767923dcd74039cc Mon Sep 17 00:00:00 2001 From: Arun Prakash Jana Date: Sun, 29 Dec 2019 23:21:18 +0530 Subject: [PATCH] Automagically handle archives --- README.md | 2 +- nnn.1 | 28 ++++++++++------- plugins/README.md | 6 ++-- src/nnn.c | 76 ++++++++++++++++++++++++++++------------------- src/nnn.h | 5 +--- 5 files changed, 69 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 4c06ef7b..16ccd8bd 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ A curses library with wide char support (e.g. ncursesw), libreadline (optional) | --- | --- | --- | | xdg-open (Linux), open(1) (macOS), cygstart (Cygwin) | base | desktop opener | | file, coreutils (cp, mv, rm), xargs | base | file type, copy, move and remove | -| tar, (un)zip [atool/bsdtar for more formats] | base | create, list, extract tar, gzip, bzip2, zip | +| tar, (un)zip [atool/bsdtar for more formats] | base | create, list, extract bzip2, (g)zip, tar | | archivemount, fusermount(3) | optional | mount, unmount archives | | sshfs, [rclone](https://rclone.org/), fusermount(3) | optional | mount, unmount remotes | | trash-cli | optional | trash files (default action: rm) | diff --git a/nnn.1 b/nnn.1 index d4a58e19..ccf2a9d9 100644 --- a/nnn.1 +++ b/nnn.1 @@ -174,6 +174,13 @@ The minimum file size unit is byte (B). The rest are K, M, G, T, P, E, Z, Y (pow The SHELL, EDITOR (VISUAL, if defined) and PAGER environment variables take precedence when dealing with the !, e and p commands respectively. A single combination to arguments is supported for SHELL and PAGER. .Pp +\fBNNN_OPENER:\fR specify a custom file opener. +.Bd -literal + export NNN_OPENER=nuke + + NOTE: `nuke` is a file opener available in plugin repository +.Ed +.Pp \fBNNN_BMS:\fR bookmark string as \fIkey_char:location\fR pairs (max 10) separated by \fI;\fR: .Bd -literal @@ -187,7 +194,11 @@ when dealing with the !, e and p commands respectively. A single combination to .Bd -literal export NNN_PLUG='o:fzopen;p:mocplay;d:diffs;m:nmount;t:imgthumb;i:mediainf' - NOTE: To run a plugin directly, press \fI:\fR followed by the plugin key. + NOTES: + 1. To run a plugin directly, press \fI;\fR followed by the plugin key + 2. To skip directory refresh after running a plugin,prefix with \fB-\fR + + export NNN_PLUG='m:-mediainfo' .Ed .Pp To assign keys to arbitrary non-background non-shell-interpreted cli @@ -198,7 +209,11 @@ when dealing with the !, e and p commands respectively. A single combination to NOTES: 1. Use single quotes for $NNN_PLUG so $nnn is not interpreted 2. $nnn should be the last argument (IF you want to pass the hovered file name) - 3. (Again) add \fI_\fR before the command + 3. (Again) add \fB_\fR before the command + 4. To disable directory refresh after running a \fIcommand as plugin\fR, prefix the command with \fB-_\fR + 5. To skip user confirmation after command execution, suffix with \fB*\fR + + export NNN_PLUG='y:-_sync*' .Ed .Pp \fBNNN_USE_EDITOR:\fR use VISUAL (else EDITOR, preferably CLI, fallback vi) to handle text files. @@ -227,13 +242,6 @@ when dealing with the !, e and p commands respectively. A single combination to NOTE: The options must be preceded by `rclone` and max 5 flags are supported. .Ed .Pp -\fBNNN_OPENER:\fR specify a custom file opener. -.Bd -literal - export NNN_OPENER=nuke - - NOTE: `nuke` is a file opener available in plugin repository -.Ed -.Pp \fBNNN_IDLE_TIMEOUT:\fR set idle timeout (in seconds) to invoke terminal locker (default: disabled). .Pp \fBNNN_TRASH:\fR trash (instead of \fIdelete\fR) files to desktop Trash. @@ -241,7 +249,7 @@ when dealing with the !, e and p commands respectively. A single combination to export NNN_TRASH=1 .Ed .Pp -\fBNNN:\fR this is a special variable set to the current entry before executing a command from the command prompt or spawning a shell. +\fBNNN:\fR this is a special variable set to the hovered entry before executing a command from the command prompt or spawning a shell. .Sh KNOWN ISSUES .Nm may not handle keypresses correctly when used with tmux (see issue #104 for more details). Set \fBTERM=xterm-256color\fR to address it. diff --git a/plugins/README.md b/plugins/README.md index 2a1aa5a8..2dd02c25 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -81,7 +81,7 @@ Plugins are installed to `${XDG_CONFIG_HOME:-$HOME/.config}/nnn/plugins`. You ca `nnn` refreshes a directory after running a plugin by key (method 1 above) to reflect any changes by the plugin. To disable this (say while running the `mediainfo` plugin on some filtered files), add a `-` before the plugin name: - export NNN_PLUG='o:fzopen;m:-mediainfo;p:mocplay; + export NNN_PLUG='m:-mediainfo' Now `nnn` will not refresh the directory after running the `mediainfo` plugin. @@ -99,7 +99,7 @@ Now ;x can be used to make a file executable, ;g can be us `nnn` waits for user confirmation (the prompt `Press Enter to continue`) after it executes a command as plugin (unlike plugins which can add a `read` to wait). To skip this, add a `*` after the command. For example: - export NNN_PLUG='x:_chmod +x $nnn;g:_git log;s:_smplayer $nnn*;o:fzopen' + export NNN_PLUG='s:_smplayer $nnn*' Now there will be no prompt after ;s. @@ -108,7 +108,7 @@ Notes: 1. Use single quotes for `$NNN_PLUG` so `$nnn` is not interpreted 2. `$nnn` should be the last argument (IF you want to pass the hovered file name) 3. (_Again_) add `_` before the command -4. To disable directory refresh after running a command as plugin prefix the command with `-_` +4. To disable directory refresh after running a _command as plugin_, prefix the command with `-_` ## Access level of plugins diff --git a/src/nnn.c b/src/nnn.c index 250d43ec..57e8fe75 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -334,6 +334,7 @@ static kv bookmark[BM_MAX]; static kv plug[PLUGIN_MAX]; static uchar g_tmpfplen; static uchar blk_shift = BLK_SHIFT_512; +static regex_t archive_re; /* Retain old signal handlers */ #ifdef __linux__ @@ -464,6 +465,7 @@ static char * const utils[] = { #define MSG_ARCHIVE_OPTS 34 #define MSG_PLUGIN_KEYS 35 #define MSG_BOOKMARK_KEYS 36 +#define MSG_INVALID_REG 37 static const char * const messages[] = { "no traversal", @@ -500,9 +502,10 @@ static const char * const messages[] = { "'s'shfs / 'r'clone?", "may take a while, try refresh", "app name: ", - "e'x'tract / 'l'ist / 'm'ount?", + "'d'efault, e'x'tract / 'l'ist / 'm'ount?", "plugin keys:", "bookmark keys:", + "invalid regex", }; /* Supported configuration environment variables */ @@ -511,9 +514,10 @@ static const char * const messages[] = { #define NNN_CONTEXT_COLORS 2 #define NNN_IDLE_TIMEOUT 3 #define NNNLVL 4 -#define NNN_PIPE 5 /* strings end here */ -#define NNN_USE_EDITOR 6 /* flags begin here */ -#define NNN_TRASH 7 +#define NNN_PIPE 5 +#define NNN_ARCHIVE 6 /* strings end here */ +#define NNN_USE_EDITOR 7 /* flags begin here */ +#define NNN_TRASH 8 static const char * const env_cfg[] = { "NNN_BMS", @@ -522,6 +526,7 @@ static const char * const env_cfg[] = { "NNN_IDLE_TIMEOUT", "NNNLVL", "NNN_PIPE", + "NNN_ARCHIVE", "NNN_USE_EDITOR", "NNN_TRASH", }; @@ -552,6 +557,7 @@ static char mv[] = "mv -i"; static const char cpmvformatcmd[] = "sed -i 's|^\\(\\(.*/\\)\\(.*\\)$\\)|#\\1\\n\\3|' %s"; static const char cpmvrenamecmd[] = "sed 's|^\\([^#][^/]\\?.*\\)$|%s/\\1|;s|^#\\(/.*\\)$|\\1|' %s | tr '\\n' '\\0' | xargs -0 -n2 sh -c '%s \"$0\" \"$@\" < /dev/tty'"; static const char batchrenamecmd[] = "paste -d'\n' %s %s | sed 'N; /^\\(.*\\)\\n\\1$/!p;d' | tr '\n' '\\0' | xargs -0 -n2 mv 2>/dev/null"; +static const char archive_regex[] ="\\.(bz|bz2|gz|tar|taz|tbz|tbz2|tgz|z|zip)$"; /* Event handling */ #ifdef LINUX_INOTIFY @@ -599,7 +605,7 @@ static int dentfind(const char *fname, int n); static void move_cursor(int target, int ignore_scrolloff); static inline bool getutil(char *util); static size_t mkpath(const char *dir, const char *name, char *out); -static char *xgetenv(const char *name, char *fallback); +static char *xgetenv(const char * const name, char *fallback); static void plugscript(const char *plugin, char *newpath, uchar flags); /* Functions */ @@ -1372,7 +1378,7 @@ static void prompt_run(char *cmd, const char *cur, const char *path) } /* Get program name from env var, else return fallback program */ -static char *xgetenv(const char *name, char *fallback) +static char *xgetenv(const char * const name, char *fallback) { char *value = getenv(name); @@ -2090,7 +2096,7 @@ static int filterentries(char *path, char *lastname) case '=': // fallthrough /* Launch app */ case ']': // fallthorugh /*Prompt key */ case ';': // fallthrough /* Run plugin key */ - case ',': // falltrough /* Pin CWD */ + case ',': // fallthrough /* Pin CWD */ case '?': /* Help and config key, '?' is an invalid regex */ if (len == 1) goto end; @@ -3480,8 +3486,8 @@ static void show_help(const char *path) "cP Copy sel here%-10c^Y Edit sel\n" "cV Move sel here%-10c^V Copy/move sel as\n" "cX Delete sel%-13c^X Delete entry\n" - "cf Archive%-16c^F Archive ops\n" "ce Edit in EDITOR%-10cp Open in PAGER\n" + "ci Archive entry%-0c\n" "1ORDER TOGGLES\n" "cS Disk usage%-14cA Apparent du\n" "cz Size%-20ct Time\n" @@ -4560,6 +4566,28 @@ nochange: continue; } + if (!regexec(&archive_re, dents[cur].name, 0, NULL, 0)) { + r = get_input(messages[MSG_ARCHIVE_OPTS]); + if (r == 'l' || r == 'x') { + mkpath(path, dents[cur].name, newpath); + handle_archive(newpath, path, r); + copycurname(); + goto begin; + } + + fd = FALSE; + if (r == 'm') { + if (!archive_mount(dents[cur].name, path, newpath, &presel)) + fd = MSG_FAILED; + } else if (r != 'd') + fd = MSG_INVALID_KEY; + + if (r != 'd') { + fd ? printwait(messages[fd], &presel) : clearprompt(); + goto nochange; + } + } + if (!sb.st_size) { printwait(messages[MSG_EMPTY_FILE], &presel); goto nochange; @@ -5324,28 +5352,6 @@ nochange: copycurname(); /* Repopulate as directory content may have changed */ goto begin; - case SEL_ARCHIVEOPS: - if (!ndents) - goto nochange; - - r = get_input(messages[MSG_ARCHIVE_OPTS]); - if (r == 'l' || r == 'x') { - mkpath(path, dents[cur].name, newpath); - handle_archive(newpath, path, r); - copycurname(); - goto begin; - } - - if (r != 'm') { - printwait(messages[MSG_INVALID_KEY], &presel); - goto nochange; - } - - if (!archive_mount(dents[cur].name, path, newpath, &presel)) { - printwait(messages[MSG_FAILED], &presel); - goto nochange; - } - // fallthrough case SEL_REMOTE: if (sel == SEL_REMOTE && !remote_mount(newpath, &presel)) goto nochange; @@ -5802,6 +5808,13 @@ int main(int argc, char *argv[]) } } + /* Set archive handling (enveditor used as tmp var) */ + enveditor = getenv(env_cfg[NNN_ARCHIVE]); + if (setfilter(&archive_re, (enveditor ? enveditor : archive_regex))) { + fprintf(stderr, "%s\n", messages[MSG_INVALID_REG]); + return _FAILURE; + } + /* Edit text in EDITOR if opted (and opener is not all-CLI) */ if (!cfg.cliopener && xgetenv_set(env_cfg[NNN_USE_EDITOR])) cfg.useeditor = 1; @@ -5928,6 +5941,9 @@ int main(int argc, char *argv[]) } else if (!cfg.picker && g_selpath) unlink(g_selpath); + /* Free the regex */ + regfree(&archive_re); + /* Free the selection buffer */ free(pselbuf); diff --git a/src/nnn.h b/src/nnn.h index 020c2575..17bcb214 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -87,7 +87,6 @@ enum action { SEL_NEW, SEL_RENAME, SEL_RENAMEMUL, - SEL_ARCHIVEOPS, SEL_REMOTE, SEL_UMOUNT, SEL_HELP, @@ -179,7 +178,7 @@ static struct key bindings[] = { /* File details */ { 'D', SEL_STATS }, /* Create archive */ - { 'f', SEL_ARCHIVE }, + { 'i', SEL_ARCHIVE }, /* Toggle sort by size */ { 'z', SEL_FSIZE }, /* Sort by apparent size including dir contents */ @@ -226,8 +225,6 @@ static struct key bindings[] = { { KEY_F(2), SEL_RENAME }, /* Rename contents of current dir */ { 'r', SEL_RENAMEMUL }, - /* Mount an archive */ - { CONTROL('F'), SEL_ARCHIVEOPS }, /* Connect to server over SSHFS */ { 'c', SEL_REMOTE }, /* Disconnect a SSHFS mount point */