Drivers have spaces inside their names, which makes tab completion tricky as it restarts for every separate argument. Support for doing this in readline and hush landed in commits 1498093ccd11 ("readline: Complete strings containing whitespaces correctly") and 70e0885229d2 ("hush: Fix handling '\ '") respectively. This only goes one way though, escape a completion suggestion. The suggestion needs to be unescaped again, so repeated <Tab> usage after modifying the prompt is understood. For this to work, skip backslashes in the argument being completed. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- v1 -> v2: - new patch to prepare completing driver names with spaces --- common/complete.c | 70 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/common/complete.c b/common/complete.c index 4137bb3084fc..ef31a36faf5f 100644 --- a/common/complete.c +++ b/common/complete.c @@ -14,6 +14,27 @@ #include <command.h> #include <environment.h> +static bool is_valid_escape(const char *str) +{ + return str[0] == '\\' && (str[1] == ' ' || str[1] == '\\'); +} + +static bool strstarts_escaped(const char *whole, const char *prefix_escaped) +{ + if (!prefix_escaped) + return true; + + while (*prefix_escaped) { + if (is_valid_escape(prefix_escaped)) + prefix_escaped++; + + if (*whole++ != *prefix_escaped++) + return false; + } + + return true; +} + static int file_complete(struct string_list *sl, char *instr, const char *dirn, int exec) { @@ -35,7 +56,7 @@ static int file_complete(struct string_list *sl, char *instr, if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; - if (strncmp(base, d->d_name, strlen(base))) + if (!strstarts_escaped(d->d_name, base)) continue; strcpy(tmp, instr); @@ -94,7 +115,7 @@ static int path_command_complete(struct string_list *sl, char *instr) !strcmp(d->d_name, "..")) continue; - if (!strncmp(instr, d->d_name, strlen(instr))) { + if (strstarts_escaped(d->d_name, instr)) { strcpy(tmp, d->d_name); if (!stat(tmp, &s) && S_ISDIR(s.st_mode)) @@ -136,15 +157,9 @@ EXPORT_SYMBOL(command_complete); int device_complete(struct string_list *sl, char *instr) { struct device *dev; - int len; - - if (!instr) - instr = ""; - - len = strlen(instr); for_each_device(dev) { - if (strncmp(instr, dev_name(dev), len)) + if (!strstarts_escaped(dev_name(dev), instr)) continue; string_list_add_asprintf(sl, "%s ", dev_name(dev)); @@ -158,12 +173,9 @@ static int device_param_complete(struct device *dev, const char *devname, struct string_list *sl, char *instr, int eval) { struct param_d *param; - int len; - - len = strlen(instr); list_for_each_entry(param, &dev->parameters, list) { - if (strncmp(instr, param->name, len)) + if (!strstarts_escaped(param->name, instr)) continue; string_list_add_asprintf(sl, "%s%s.%s%c", @@ -340,21 +352,43 @@ void complete_reset(void) tab_pressed = 0; } +static char *skip_to_last_unescaped_space(char *instr) +{ + char *t; + + t = strrchr(instr, ' '); + if (t && (instr == t || t[-1] != '\\')) + return t + 1; + + return instr; +} + +static size_t strlen_escaped(char *instr) +{ + size_t count = 0; + + for (; *instr; instr++) { + if (is_valid_escape(instr)) + instr++; + + count++; + } + + return count; +} + static char* cmd_complete_lookup(struct string_list *sl, char *instr) { struct command *cmdtp; int len; int ret = COMPLETE_END; char *res = NULL; - char *t; for_each_command(cmdtp) { len = strlen(cmdtp->name); if (!strncmp(instr, cmdtp->name, len) && instr[len] == ' ') { instr += len + 1; - t = strrchr(instr, ' '); - if (t) - instr = t + 1; + instr = skip_to_last_unescaped_space(instr); if (cmdtp->complete) { ret = cmdtp->complete(sl, instr); @@ -414,7 +448,7 @@ int complete(char *instr, char **outstr) env_param_complete(&sl, instr + 1, 1); } - pos = strlen(instr); + pos = strlen_escaped(instr); *outstr = ""; if (list_empty(&sl.list)) -- 2.39.2