Add a “-c” option which enables tab-completion for file-names. With it enabled, when entered text contains multiple words (for example “soffice docs”) or contains a slash (for example “~/bin”), pressing C-i or Tab will expand the last argument as a file name.
If there were multiple expansions the longest common prefix is substituted. Otherwise, if there was exactly one expansion a slash or space is added ofter it depending on whether it expanded to a directory name or not. One known limitation is that if expanded file name contains white-space, user will have to quote the argument herself or otherwise when executing it will likely be interpreted as separate arguments. --- LICENSE | 2 ++ dmenu.1 | 11 +++++++- dmenu.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 106 insertions(+), 4 deletions(-) This is based on 2009 patch by Jeremy Jay which can be found at <http://lists.suckless.org/dwm/0901/7355.html>, but lacks buffer overflow bug. ;) diff --git a/LICENSE b/LICENSE index 39c4b6e..8658346 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,12 @@ MIT/X Consortium License +© 2014 Google Inc. // author: Michal Nazarewicz <min...@mina86.com> © 2006-2014 Anselm R Garbe <ans...@garbe.us> © 2010-2012 Connor Lane Smith <c...@lubutu.com> © 2009 Gottox <got...@s01.de> © 2009 Markus Schnalke <mei...@marmaro.de> © 2009 Evan Gates <evan.ga...@gmail.com> +© 2009 Jeremy Jay © 2006-2008 Sander van Dijk <a dot h dot vandijk at gmail dot com> © 2006-2007 Michał Janeczek <janeczek at gmail dot com> diff --git a/dmenu.1 b/dmenu.1 index bbee17d..af78643 100644 --- a/dmenu.1 +++ b/dmenu.1 @@ -6,6 +6,7 @@ dmenu \- dynamic menu .RB [ \-b ] .RB [ \-f ] .RB [ \-i ] +.RB [ \-c ] .RB [ \-l .RB [ \-m .IR monitor ] @@ -48,6 +49,9 @@ X until stdin reaches end\-of\-file. .B \-i dmenu matches menu items case insensitively. .TP +.B \-c +enables file-name expansion when C\-i (or Tab) is pressed. +.TP .BI \-l " lines" dmenu lists items vertically, with the given number of lines. .TP @@ -82,7 +86,12 @@ dmenu is completely controlled by the keyboard. Items are selected using the arrow keys, page up, page down, home, and end. .TP .B Tab -Copy the selected item to the input field. +If \-c option is in effect and the text consists of at least two words +or contains a slash character, perform a word expansion on the last +word (which may be the only word if it contains a slash). + +If \-c is not given or the above conditions are not met, copy the +selected item to the input field. .TP .B Return Confirm selection. Prints the selected item to stdout and exits, returning diff --git a/dmenu.c b/dmenu.c index dd2c128..4073fb8 100644 --- a/dmenu.c +++ b/dmenu.c @@ -4,7 +4,10 @@ #include <stdlib.h> #include <string.h> #include <strings.h> +#include <sys/types.h> +#include <sys/stat.h> #include <unistd.h> +#include <wordexp.h> #include <X11/Xlib.h> #include <X11/Xatom.h> #include <X11/Xutil.h> @@ -35,6 +38,7 @@ static void keypress(XKeyEvent *ev); static void match(void); static size_t nextrune(int inc); static void paste(void); +static Bool matchfile_maybe(void); static void readstdin(void); static void run(void); static void setup(void); @@ -47,6 +51,7 @@ static size_t cursor = 0; static unsigned long normcol[ColLast]; static unsigned long selcol[ColLast]; static unsigned long outcol[ColLast]; +static Bool matchfile_enabled = False; static Atom clip, utf8; static DC *dc; static Item *items = NULL; @@ -80,6 +85,9 @@ main(int argc, char *argv[]) { fstrncmp = strncasecmp; fstrstr = cistrstr; } + else if(!strcmp(argv[i], "-c")) { /* file name tab completion */ + matchfile_enabled = True; + } else if(i+1 == argc) usage(); /* these options take one argument */ @@ -227,7 +235,8 @@ insert(const char *str, ssize_t n) { if(strlen(text) + n > sizeof text - 1) return; /* move existing text out of the way, insert new text, and update cursor */ - memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + memmove(&text[cursor + n], &text[cursor], + sizeof text - cursor - MAX(n, 0)); if(n > 0) memcpy(&text[cursor], str, n); cursor += n; @@ -387,6 +396,8 @@ keypress(XKeyEvent *ev) { } break; case XK_Tab: + if (matchfile_maybe()) + break; if(!sel) return; strncpy(text, sel->text, sizeof text - 1); @@ -476,6 +487,86 @@ paste(void) { drawmenu(); } +Bool +matchfile_maybe(void) { + static int wrde_flags; + static wordexp_t exp; + + char *const end = text + (sizeof text - 1); + char *ch, *src, *word = NULL; + struct stat buf; + unsigned i; + + if (!matchfile_enabled) { + return False; + } + + /* Expansion supported only at the end of line at this point. */ + if (text[cursor]) { + return False; + } + + /* Need enough space to insert star. */ + if (cursor + 1 >= sizeof text) { + return False; + } + + /* Do file match expansion if text consists of multiple words + * or the first word contains slashes. */ + for (ch = text; *ch; ++ch) { + if (isspace(*ch)) { + word = ch + 1; + } else if (!word && *ch == '/') { + word = text; + } + } + + if (!word || !*word) { + return False; + } + + /* Perform expansion */ + ch[0] = '*'; + ch[1] = 0; + + if (wordexp(word, &exp, wrde_flags) || + (wrde_flags |= WRDE_REUSE, !exp.we_wordc) || + !strcmp(word, exp.we_wordv[0])) { + *ch = 0; /* Eat "*" */ + return True; + } + + /* Check if anything actually changed */ + + /* Copy the first expansion */ + ch = word; + src = exp.we_wordv[0]; + while (ch != end && (*ch = *src++)) { + ++ch; + } + *ch = 0; + + /* Compare with all the other expansions so we get the common part. */ + for (i = 1; i < exp.we_wordc; ++i) { + ch = word; + src = exp.we_wordv[i]; + while (*ch && *ch == *src++) { + ++ch; + } + *ch = 0; + } + + /* If there was only one match, add slash or space */ + if (exp.we_wordc == 1 && ch != end && *ch != '/' && + stat(word, &buf) == 0) { + *ch++ = S_ISDIR(buf.st_mode) ? '/' : ' '; + *ch = 0; + } + + cursor = ch - text; + return True; +} + void readstdin(void) { char buf[sizeof text], *p, *maxstr = NULL; @@ -619,7 +710,7 @@ setup(void) { void usage(void) { - fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" - " [-nb color] [-nf color] [-sb color] [-sf color] [-v]\n", stderr); + fputs("usage: dmenu [-b] [-c] [-f] [-i] [-v] [-l lines] [-p prompt] [-fn font]\n" + " [-m monitor] [-nb color] [-nf color] [-sb color] [-sf color]\n", stderr); exit(EXIT_FAILURE); } -- 2.0.0.526.g5318336