[git commit master 1/1] lineedit: fix tab-completion of filenames with spaces

Denys Vlasenko vda.linux at googlemail.com
Mon Nov 22 02:49:18 UTC 2010


commit: http://git.busybox.net/busybox/commit/?id=f3763033e457b100a863731c16a80648d85995c9
branch: http://git.busybox.net/busybox/commit/?id=refs/heads/master

Using ash in busybox git version dea28e1e, tab completion doesn't seem
to work properly for filenames that have special characters (such as
spaces) in them. For example, with filenames "foo bar" and "foo zap",
typing "ls fo<TAB>" correctly expands to "ls foo\ ", but then
continuing to type "b<TAB>" will produce "ls foo\ bbar", which is not
correct (the 'b' is duplicated).

Signed-off-by: Mike Shal <marfey at gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 libbb/lineedit.c |   67 ++++++++++++++++++++++++++++++++++++-----------------
 1 files changed, 45 insertions(+), 22 deletions(-)

diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index d6c3354..5dd835c 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -584,6 +584,12 @@ static void input_forward(void)
 
 #if ENABLE_FEATURE_TAB_COMPLETION
 
+//FIXME:
+//needs to be more clever: currently it thinks that "foo\ b<TAB>
+//matches the file named "foo bar", which is untrue.
+//Also, perhaps "foo b<TAB> needs to complete to "foo bar" <cursor>,
+//not "foo bar <cursor>...
+
 static void free_tab_completion_data(void)
 {
 	if (matches) {
@@ -601,7 +607,7 @@ static void add_match(char *matched)
 	num_matches++;
 }
 
-#if ENABLE_FEATURE_USERNAME_COMPLETION
+# if ENABLE_FEATURE_USERNAME_COMPLETION
 /* Replace "~user/..." with "/homedir/...".
  * The parameter is malloced, free it or return it
  * unchanged if no user is matched.
@@ -657,7 +663,7 @@ static NOINLINE unsigned complete_username(const char *ud)
 
 	return 1 + userlen;
 }
-#endif  /* FEATURE_USERNAME_COMPLETION */
+# endif  /* FEATURE_USERNAME_COMPLETION */
 
 enum {
 	FIND_EXE_ONLY = 0,
@@ -734,10 +740,10 @@ static NOINLINE unsigned complete_cmd_dir_file(const char *command, int type)
 		pfind++;
 		/* dirbuf = ".../.../.../" */
 		dirbuf = xstrndup(command, pfind - command);
-#if ENABLE_FEATURE_USERNAME_COMPLETION
+# if ENABLE_FEATURE_USERNAME_COMPLETION
 		if (dirbuf[0] == '~')   /* ~/... or ~user/... */
 			dirbuf = username_path_completion(dirbuf);
-#endif
+# endif
 		path1[0] = dirbuf;
 	}
 	pf_len = strlen(pfind);
@@ -1015,13 +1021,18 @@ static void showfiles(void)
 	}
 }
 
-static char *add_quote_for_spec_chars(char *found)
+static const char *is_special_char(char c)
+{
+	return strchr(" `\"#$%^&*()=+{}[]:;'|\\<>", c);
+}
+
+static char *quote_special_chars(char *found)
 {
 	int l = 0;
 	char *s = xzalloc((strlen(found) + 1) * 2);
 
 	while (*found) {
-		if (strchr(" `\"#$%^&*()=+{}[]:;'|\\<>", *found))
+		if (is_special_char(*found))
 			s[l++] = '\\';
 		s[l++] = *found++;
 	}
@@ -1038,10 +1049,10 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 	/* Length of string used for matching */
 	unsigned match_pfx_len = match_pfx_len;
 	int find_type;
-#if ENABLE_UNICODE_SUPPORT
+# if ENABLE_UNICODE_SUPPORT
 	/* cursor pos in command converted to multibyte form */
 	int cursor_mb;
-#endif
+# endif
 	if (!(state->flags & TAB_COMPLETION))
 		return;
 
@@ -1068,9 +1079,9 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 	 * (we then also (ab)use this extra space later - see (**))
 	 */
 	match_buf = xmalloc(MAX_LINELEN * sizeof(int16_t));
-#if !ENABLE_UNICODE_SUPPORT
+# if !ENABLE_UNICODE_SUPPORT
 	save_string(match_buf, cursor + 1); /* +1 for NUL */
-#else
+# else
 	{
 		CHAR_T wc = command_ps[cursor];
 		command_ps[cursor] = BB_NUL;
@@ -1078,26 +1089,37 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 		command_ps[cursor] = wc;
 		cursor_mb = strlen(match_buf);
 	}
-#endif
+# endif
 	find_type = build_match_prefix(match_buf);
 
 	/* Free up any memory already allocated */
 	free_tab_completion_data();
 
-#if ENABLE_FEATURE_USERNAME_COMPLETION
-	/* If the word starts with `~' and there is no slash in the word,
+# if ENABLE_FEATURE_USERNAME_COMPLETION
+	/* If the word starts with ~ and there is no slash in the word,
 	 * then try completing this word as a username. */
 	if (state->flags & USERNAME_COMPLETION)
 		if (match_buf[0] == '~' && strchr(match_buf, '/') == NULL)
 			match_pfx_len = complete_username(match_buf);
-#endif
-	/* Try to match a command in $PATH, or a directory, or a file */
+# endif
+	/* If complete_username() did not match,
+	 * try to match a command in $PATH, or a directory, or a file */
 	if (!matches)
 		match_pfx_len = complete_cmd_dir_file(match_buf, find_type);
+
+	/* Account for backslashes which will be inserted
+	 * by quote_special_chars() later */
+	{
+		const char *e = match_buf + strlen(match_buf);
+		const char *s = e - match_pfx_len;
+		while (s < e)
+			if (is_special_char(*s++))
+				match_pfx_len++;
+	}
+
 	/* Remove duplicates */
 	if (matches) {
-		unsigned i;
-		unsigned n = 0;
+		unsigned i, n = 0;
 		qsort_string_vector(matches, num_matches);
 		for (i = 0; i < num_matches - 1; ++i) {
 			//if (matches[i] && matches[i+1]) { /* paranoia */
@@ -1112,6 +1134,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 		matches[n++] = matches[i];
 		num_matches = n;
 	}
+
 	/* Did we find exactly one match? */
 	if (num_matches != 1) { /* no */
 		char *cp;
@@ -1133,7 +1156,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 			goto ret; /* no */
 		}
 		*cp = '\0';
-		cp = add_quote_for_spec_chars(chosen_match);
+		cp = quote_special_chars(chosen_match);
 		free(chosen_match);
 		chosen_match = cp;
 		len_found = strlen(chosen_match);
@@ -1141,7 +1164,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 		/* Next <tab> is not a double-tab */
 		*lastWasTab = 0;
 
-		chosen_match = add_quote_for_spec_chars(matches[0]);
+		chosen_match = quote_special_chars(matches[0]);
 		len_found = strlen(chosen_match);
 		if (chosen_match[len_found-1] != '/') {
 			chosen_match[len_found] = ' ';
@@ -1149,7 +1172,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 		}
 	}
 
-#if !ENABLE_UNICODE_SUPPORT
+# if !ENABLE_UNICODE_SUPPORT
 	/* Have space to place the match? */
 	/* The result consists of three parts with these lengths: */
 	/* cursor + (len_found - match_pfx_len) + (command_len - cursor) */
@@ -1166,7 +1189,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 		/* write out the matched command */
 		redraw(cmdedit_y, command_len - pos);
 	}
-#else
+# else
 	{
 		/* Use 2nd half of match_buf as scratch space - see (**) */
 		char *command = match_buf + MAX_LINELEN;
@@ -1190,7 +1213,7 @@ static NOINLINE void input_tab(smallint *lastWasTab)
 			redraw(cmdedit_y, pos >= 0 ? pos : 0);
 		}
 	}
-#endif
+# endif
  ret:
 	free(chosen_match);
 	free(match_buf);
-- 
1.7.2.2



More information about the busybox-cvs mailing list