[dev] [PATCH] Add tab-completion file-name expansion.

From: Michal Nazarewicz <mina86_AT_mina86.com>
Date: Sun, 06 Jul 2014 20:19:15 +0200

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
_AT_@ -1,10 +1,12 @@
 MIT/X Consortium License
 
+© 2014 Google Inc.  // author: Michal Nazarewicz <mina86_AT_mina86.com>
 © 2006-2014 Anselm R Garbe <anselm_AT_garbe.us>
 © 2010-2012 Connor Lane Smith <cls_AT_lubutu.com>
 © 2009 Gottox <gottox_AT_s01.de>
 © 2009 Markus Schnalke <meillo_AT_marmaro.de>
 © 2009 Evan Gates <evan.gates_AT_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
_AT_@ -6,6 +6,7 @@ dmenu \- dynamic menu
 .RB [ \-b ]
 .RB [ \-f ]
 .RB [ \-i ]
+.RB [ \-c ]
 .RB [ \-l
 .RB [ \-m
 .IR monitor ]
_AT_@ -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
_AT_@ -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
_AT_@ -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>
_AT_@ -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);
_AT_@ -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;
_AT_@ -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 */
_AT_@ -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;
_AT_@ -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);
_AT_@ -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;
_AT_@ -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
Received on Sun Jul 06 2014 - 20:19:15 CEST

This archive was generated by hypermail 2.3.0 : Sun Jul 06 2014 - 20:24:07 CEST