mail archive of the barebox mailing list
 help / color / mirror / Atom feed
From: Ahmad Fatoum <a.fatoum@pengutronix.de>
To: barebox@lists.infradead.org
Cc: Ahmad Fatoum <a.fatoum@pengutronix.de>
Subject: [PATCH v2 2/3] complete: add support for spaces in completions
Date: Thu,  9 Nov 2023 13:25:50 +0100	[thread overview]
Message-ID: <20231109122551.1486020-2-a.fatoum@pengutronix.de> (raw)
In-Reply-To: <20231109122551.1486020-1-a.fatoum@pengutronix.de>

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@pengutronix.de>
---
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




  reply	other threads:[~2023-11-09 12:27 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-09 12:25 [PATCH v2 1/3] glob: drop needless ifdeffery in {glob,fnmatch}.h Ahmad Fatoum
2023-11-09 12:25 ` Ahmad Fatoum [this message]
2023-11-09 12:25 ` [PATCH v2 3/3] commands: drvinfo: support filtering by driver Ahmad Fatoum
2023-11-13 12:51 ` [PATCH v2 1/3] glob: drop needless ifdeffery in {glob,fnmatch}.h Sascha Hauer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231109122551.1486020-2-a.fatoum@pengutronix.de \
    --to=a.fatoum@pengutronix.de \
    --cc=barebox@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox