| kdeconnect | Send selected files to an Android device | sh | kdeconnect-cli |
| launch | GUI application launcher | sh | fzf/fzy |
| mediainf | Show media information | sh | mediainfo |
+| mimelist | List files by mime in subtree | sh | fd/find |
| moclyrics | Show lyrics of the track playing in moc | sh | [ddgr](https://github.com/jarun/ddgr), [moc](http://moc.daper.net/) |
| mocplay | Append (and/or play) selection/dir/file in moc | sh | [moc](http://moc.daper.net/) |
| mp3conv | Extract audio from multimedia as mp3 | sh | ffmpeg |
#### 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).
+The plugin should write a single string in the format `<context number><char><path>` without a newline at the end. For example, `1c/etc`.
+The context number indicates the context to change the active directory of (0 is used to indicate the current context).
+The `<char>` indicates the operation type.
+
+: Char : Operation :
+|:---:| --- |
+| c | cd |
+| l | list files in list mode |
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. To skip this and choose the current context, set the `CUR_CTX` variable in `.nnn-plugin-helper` to `1`.
printf "cd to: "
read -r dir
- printf "%s" "0$dir" > "$NNN_PIPE"
+ printf "%s" "0c$dir" > "$NNN_PIPE"
```
## Contributing plugins
#define MSG_RM_TMP 40
#define MSG_NOCHNAGE 41
#define MSG_CANCEL 42
+#define MSG_0_ENTRIES 43
#ifndef DIR_LIMITED_SELECTION
-#define MSG_DIR_CHANGED 43 /* Must be the last entry */
+#define MSG_DIR_CHANGED 44 /* Must be the last entry */
#endif
static const char * const messages[] = {
"unchanged",
"cancelled",
"first file (\')/char?",
+ "0 entries",
#ifndef DIR_LIMITED_SELECTION
"dir changed, range sel off", /* Must be the last entry */
#endif
static size_t mkpath(const char *dir, const char *name, char *out);
static char *xgetenv(const char *name, char *fallback);
static bool plugscript(const char *plugin, const char *path, uchar flags);
+static char *load_input(int fd, char *path);
/* Functions */
return _SUCCESS;
}
+static void rmlistpath()
+{
+ if (listpath) {
+ DPRINTF_S(__FUNCTION__);
+ DPRINTF_S(initpath);
+ spawn("rm -rf", initpath, NULL, NULL, F_NOTRACE | F_MULTI);
+ listpath = NULL;
+ }
+}
+
+static void readpipe(int fd, char **path, char **lastname, char **lastdir)
+{
+ char *nextpath = NULL;
+ ssize_t len = read(fd, g_buf, 1);
+
+ if (len != 1)
+ return;
+
+ char ctx = g_buf[0] - '0';
+
+ if (ctx > CTX_MAX)
+ return;
+
+ len = read(fd, g_buf, 1);
+ if (len != 1)
+ return;
+
+ char op = g_buf[0];
+
+ if (op == 'c') {
+ len = read(fd, g_buf, PATH_MAX);
+ if (len <= 0)
+ return;
+
+ nextpath = g_buf;
+ } else if (op == 'l') {
+ /* Remove last list mode path, if any */
+ rmlistpath();
+
+ nextpath = load_input(fd, *path);
+ if (nextpath) {
+ free(initpath);
+ initpath = nextpath;
+ DPRINTF_S(initpath);
+ }
+ }
+
+ if (nextpath) {
+ if (ctx == 0 || ctx == cfg.curctx + 1) {
+ xstrsncpy(*lastdir, *path, PATH_MAX);
+ xstrsncpy(*path, nextpath, PATH_MAX);
+ } else {
+ int r = ctx - 1;
+
+ g_ctx[r].c_cfg.ctxactive = 0;
+ savecurctx(&cfg, nextpath, dents[cur].name, r);
+ *path = g_ctx[r].c_path;
+ *lastdir = g_ctx[r].c_last;
+ *lastname = g_ctx[r].c_name;
+ }
+ }
+}
+
static bool run_selected_plugin(char **path, const char *file, char *runfile, char **lastname, char **lastdir)
{
int fd;
- size_t len;
-
if (!(g_states & STATE_PLUGIN_INIT)) {
plctrl_init();
g_states |= STATE_PLUGIN_INIT;
spawn(g_buf, NULL, *path, *path, F_NORMAL);
}
- 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 || ctx == cfg.curctx + 1) {
- xstrsncpy(*lastdir, *path, PATH_MAX);
- xstrsncpy(*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;
- }
- }
+ readpipe(fd, path, lastname, lastdir);
+ close(fd);
return TRUE;
}
struct stat sb;
char *slash, *tmp;
ssize_t len = xstrlen(prefix);
- char *tmpdir = malloc(sizeof(char) * (PATH_MAX + TMP_LEN_MAX));
+ char *tmpdir = malloc(PATH_MAX);
if (!tmpdir) {
DPRINTF_S(strerror(errno));
return tmpdir;
}
-static char *load_input()
+static char *load_input(int fd, char *path)
{
/* 512 KiB chunk size */
ssize_t i, chunk_count = 1, chunk = 512 * 1024, entries = 0;
return NULL;
}
- if (!getcwd(cwd, PATH_MAX)) {
- free(input);
- return NULL;
- }
+ if (!path) {
+ if (!getcwd(cwd, PATH_MAX)) {
+ free(input);
+ return NULL;
+ }
+ } else
+ xstrsncpy(cwd, path, PATH_MAX);
while (chunk_count < 512) {
- input_read = read(STDIN_FILENO, input + total_read, chunk);
+ input_read = read(fd, input + total_read, chunk);
if (input_read < 0) {
DPRINTF_S(strerror(errno));
goto malloc_1;
DPRINTF_D(chunk_count);
if (!entries) {
- fprintf(stderr, "0 entries\n");
+ if (g_states & STATE_PLUGIN_INIT) {
+ printmsg(messages[MSG_0_ENTRIES]);
+ xdelay(XDELAY_INTERVAL_MS);
+ } else
+ fprintf(stderr, "%s\n", messages[MSG_0_ENTRIES]);
goto malloc_1;
}
/* Check if we are in path list mode */
if (!isatty(STDIN_FILENO)) {
/* This is the same as listpath */
- initpath = load_input();
+ initpath = load_input(STDIN_FILENO, NULL);
if (!initpath)
return _FAILURE;
unlink(selpath);
/* Remove tmp dir in list mode */
- if (listpath)
- spawn("rm -rf", initpath, NULL, NULL, F_NOTRACE | F_MULTI);
+ rmlistpath();
/* Free the regex */
#ifdef PCRE