diff --git a/README.md b/README.md index 4dc47363..67ce1430 100644 --- a/README.md +++ b/README.md @@ -211,8 +211,9 @@ The list below is from the **dev branch**. Press ? in `nnn` to see th ⎵ ^J Select entry r Batch rename m ^K Sel range, clear M List selection a Select all K Edit selection - P Copy selection X Delete selection - V Move selection ^X Delete entry + P Copy selection w Copy selection as + V Move selection W Move selection as + X Delete selection ^X Delete entry f Create archive T Mount archive ^F Extract archive F List archive e Edit in EDITOR p Open in PAGER diff --git a/src/nnn.c b/src/nnn.c index b18f3d79..093cc37e 100644 --- a/src/nnn.c +++ b/src/nnn.c @@ -390,7 +390,7 @@ static char mv[] = "mvg -gi"; #define STR_TMPFILE 3 #define NONE_SELECTED 4 #define UTIL_MISSING 5 -#define MOUNT_FAILED 6 +#define OPERATION_FAILED 6 static const char * const messages[] = { "no traversal", @@ -399,7 +399,7 @@ static const char * const messages[] = { "/.nnnXXXXXX", "0 selected", "missing dep", - "mount failed", + "failed!", }; /* Supported configuration environment variables */ @@ -1280,6 +1280,70 @@ static void xrm(char *path) } } +static bool cpmv_rename(const char *path, const char *cmd) +{ + int fd, i; + uint count = 0, lines = 0; + bool ret = FALSE; + const char formatcmd[] = "sed -i 's|^\\(\\(.*/\\)\\(.*\\)$\\)|#\\1\\n\\3|' %s"; + const char renamecmd[] = + "sed 's|^\\([^#][^/]\\?.*\\)$|%s/\\1|;s|^#\\(/.*\\)$|\\1|' %s | tr '\\n' '\\0' | xargs -0 -o -n2 %s"; + char buf[sizeof(renamecmd) + sizeof(cmd) + (PATH_MAX << 1)]; + + if ((fd = create_tmp_file()) == -1) + return ret; + + /* selsafe() returned TRUE for this to be called */ + if (!selbufpos) { + snprintf(buf, sizeof(buf), "cat %s | tr '\\0' '\\n' > %s", g_selpath, g_tmpfpath); + spawn("sh", "-c", buf, NULL, F_NORMAL | F_CMD); + + while ((i = read(fd, buf, sizeof(buf))) > 0) + while(i) + count += (buf[--i] == '\n'); + + if (!count) + goto finish; + } else { + seltofile(fd, &count); + } + + close(fd); + + snprintf(buf, sizeof(buf), formatcmd, g_tmpfpath); + spawn("sh", "-c", buf, path, F_NORMAL); + + spawn(editor, g_tmpfpath, NULL, path, F_CLI); + + if ((fd = open(g_tmpfpath, O_RDONLY)) == -1) + goto finish; + + while ((i = read(fd, buf, sizeof(buf))) > 0) + while (i) + lines += (buf[--i] == '\n'); + + if (i < 0) + goto finish; + + DPRINTF_U(count); + DPRINTF_U(lines); + + if (2 * count != lines) { + DPRINTF_S("cannot delete files"); + goto finish; + } + + snprintf(buf, sizeof(buf), renamecmd, path, g_tmpfpath, cmd); + spawn("sh", "-c", buf, path, F_NORMAL); + ret = TRUE; + +finish: + if (fd >= 0) + close(fd); + + return ret; +} + static bool batch_rename(const char *path) { int fd1, fd2, i; @@ -2847,7 +2911,7 @@ static bool archive_mount(char *name, char *path, char *newpath, int *presel) DPRINTF_S(name); DPRINTF_S(newpath); if (spawn(cmd, name, newpath, path, F_NORMAL)) { - printwait(messages[MOUNT_FAILED], presel); + printwait(messages[OPERATION_FAILED], presel); return FALSE; } @@ -2889,7 +2953,7 @@ static bool sshfs_mount(char *newpath, int *presel) /* Connect to remote */ if (spawn(env, tmp, newpath, NULL, flag)) { - printwait(messages[MOUNT_FAILED], presel); + printwait(messages[OPERATION_FAILED], presel); return FALSE; } @@ -2939,7 +3003,7 @@ static bool unmount(char *name, char *newpath, int *presel, char *currentpath) } if (spawn(cmd, "-u", newpath, NULL, F_NORMAL)) { - printwait("unmount failed", presel); + printwait(messages[OPERATION_FAILED], presel); return FALSE; } @@ -2997,8 +3061,9 @@ static void show_help(const char *path) "9⎵ ^J Select entry r Batch rename\n" "9m ^K Sel range, clear M List selection\n" "ca Select all K Edit selection\n" - "cP Copy selection X Delete selection\n" - "cV Move selection ^X Delete entry\n" + "cP Copy selection w Copy selection as\n" + "cV Move selection W Move selection as\n" + "cX Delete selection ^X Delete entry\n" "cf Create archive T Mount archive\n" "b^F Extract archive F List archive\n" "ce Edit in EDITOR p Open in PAGER\n" @@ -4187,7 +4252,7 @@ nochange: endselection(); if (!batch_rename(path)) { - printwait("batch rename failed", &presel); + printwait(messages[OPERATION_FAILED], &presel); goto nochange; } break; @@ -4319,12 +4384,14 @@ nochange: goto nochange; case SEL_SELEDIT: if (!seledit()){ - printwait("edit failed!", &presel); + printwait(messages[OPERATION_FAILED], &presel); goto nochange; } break; case SEL_CP: case SEL_MV: + case SEL_CPAS: + case SEL_MVAS: case SEL_RMMUL: { endselection(); @@ -4341,15 +4408,28 @@ nochange: case SEL_MV: mvstr(g_buf); break; + case SEL_CPAS: + if (!cpmv_rename(path, cp)) { + printwait(messages[OPERATION_FAILED], &presel); + goto nochange; + } + break; + case SEL_MVAS: + if (!cpmv_rename(path, mv)) { + printwait(messages[OPERATION_FAILED], &presel); + goto nochange; + } + break; default: /* SEL_RMMUL */ rmmulstr(g_buf); break; } - spawn("sh", "-c", g_buf, path, F_NORMAL); + if (sel != SEL_CPAS && sel != SEL_MVAS) + spawn("sh", "-c", g_buf, path, F_NORMAL); /* Clear selection on move or delete */ - if (sel == SEL_MV || sel == SEL_RMMUL) { + if (sel == SEL_MV || sel == SEL_MVAS || sel == SEL_RMMUL) { nselected = 0; selbufpos = 0; writesel(NULL, 0); diff --git a/src/nnn.h b/src/nnn.h index 2933d264..b6f4886f 100644 --- a/src/nnn.h +++ b/src/nnn.h @@ -82,6 +82,8 @@ enum action { SEL_SELEDIT, SEL_CP, SEL_MV, + SEL_CPAS, + SEL_MVAS, SEL_RMMUL, SEL_RM, SEL_OPENWITH, @@ -217,6 +219,10 @@ static struct key bindings[] = { { 'P', SEL_CP }, /* Move from selection buffer */ { 'V', SEL_MV }, + /* Copyfrom selection buffer and rename */ + { 'w', SEL_CPAS }, + /* Movefrom selection buffer and rename */ + { 'W', SEL_MVAS }, /* Delete from selection buffer */ { 'X', SEL_RMMUL }, /* Delete currently selected */