[PATCH] Re: Tab completion

Daniel Reed n at cs.rpi.edu
Fri Apr 11 07:14:17 GMT 2003


On 2003-04-10T22:37+0200, Jelmer Vernooij wrote:
) On Thursday 10 April 2003 22:25, Daniel Reed wrote:
) > Is anyone working on Tab completion in smbclient? I see a framework in
) > place, but little support code. I'd be interested in looking into it if
) > it's stagnant.
) There's nobody working at it currently. Patches are welcome.

This patch changes the behaviour of the completion_fn() function. File
offsets are based on having my earlier patch applied, but patch 2.5.4 (at
least) is able to find the appropriate locations in stock 2.2.8a.

If Samba is compiled without GNU readline support, no behaviour is changed.

If compiled with GNU readline support, smbclient will ask readline to use an
internal completion_fn() routine for command-line Tab completion:
	If Tab is pressed when there are no spaces on the command line,
completion_fn() will complete command names (so ``ge<Tab>'' expands to
``get '').
	If Tab is pressed while typing an argument smbclient expects to be a
remote file name, completion_fn() will complete remote files (so
``get ma<Tab>'' will expand to ``get mail/'', and ``get mail/sen<Tab>'' will
expand to ``get mail/sent-mail '').
	Otherwise, GNU readline's internal local filename completion will be
used.

-- 
Daniel Reed <n at cs.rpi.edu>	http://s.acm.rpi.edu/~n/
naim FAQ: http://128.113.139.111/~n/naim/FAQ
-------------- next part --------------
diff -ruN samba-2.2.8a,original/source/client/client.c samba-2.2.8a/source/client/client.c
--- samba-2.2.8a,original/source/client/client.c	2003-04-11 02:33:07.000000000 -0400
+++ samba-2.2.8a/source/client/client.c	2003-04-11 02:32:42.000000000 -0400
@@ -2204,40 +2204,188 @@
 	}
 }	
 
+#ifdef HAVE_LIBREADLINE
 /****************************************************************************
 handle completion of commands for readline
 ****************************************************************************/
-static char **completion_fn(const char *text, int start, int end)
-{
+
 #define MAX_COMPLETIONS 100
-	char **matches;
-	int i, count=0;
 
-	/* for words not at the start of the line fallback to filename completion */
-	if (start) return NULL;
+static char **completion_commands(const char *text, int len)
+{
+	char **matches;
+	int i, count=1, samelen=len;
 
 	matches = (char **)malloc(sizeof(matches[0])*MAX_COMPLETIONS);
 	if (!matches) return NULL;
-
-	matches[count++] = strdup(text);
-	if (!matches[0]) return NULL;
+	matches[0] = NULL;
 
 	for (i=0;commands[i].fn && count < MAX_COMPLETIONS-1;i++) {
-		if (strncmp(text, commands[i].name, strlen(text)) == 0) {
+		if (strncmp(text, commands[i].name, len) == 0) {
 			matches[count] = strdup(commands[i].name);
-			if (!matches[count]) return NULL;
+			if (!matches[count])
+				goto cleanup;
+			if (count == 1)
+				samelen = strlen(matches[count]);
+			else
+				while (strncmp(matches[count], matches[count-1], samelen) != 0)
+					samelen--;
 			count++;
 		}
 	}
 
-	if (count == 2) {
-		SAFE_FREE(matches[0]);
+	if (count == 2)
 		matches[0] = strdup(matches[1]);
+	else {
+		matches[0] = malloc(samelen+1);
+		if (!matches[0])
+			goto cleanup;
+		strncpy(matches[0], matches[1], samelen);
+		matches[0][samelen] = 0;
 	}
 	matches[count] = NULL;
 	return matches;
+
+ cleanup:
+	while (i >= 0) {
+		free(matches[i]);
+		i--;
+	}
+	free(matches);
+	return NULL;
 }
 
+typedef struct {
+	pstring dirmask;
+	char **matches;
+	int count, samelen;
+	const char *text;
+	int len;
+} completion_remote_t;
+
+static void completion_remote_filter(file_info *f, const char *mask, void *state)
+{
+	completion_remote_t *info = (completion_remote_t *)state;
+
+	if ((info->count < MAX_COMPLETIONS-1) && (strncmp(info->text, f->name, info->len) == 0) && (strcmp(f->name, ".") != 0) && (strcmp(f->name, "..") != 0)) {
+		if ((info->dirmask[0] == 0) && !(f->mode & aDIR))
+			info->matches[info->count] = strdup(f->name);
+		else {
+			pstring tmp;
+
+			if (info->dirmask[0] != 0)
+				pstrcpy(tmp, info->dirmask);
+			else
+				tmp[0] = 0;
+			pstrcat(tmp, f->name);
+			if (f->mode & aDIR)
+				pstrcat(tmp, "/");
+			info->matches[info->count] = strdup(tmp);
+		}
+		if (info->matches[info->count] == NULL)
+			return;
+		if (f->mode & aDIR)
+			rl_completion_append_character = 0;
+		
+		if (info->count == 1)
+			info->samelen = strlen(info->matches[info->count]);
+		else
+			while (strncmp(info->matches[info->count], info->matches[info->count-1], info->samelen) != 0)
+				info->samelen--;
+		info->count++;
+	}
+}
+
+static char **completion_remote(const char *text, int len)
+{
+	pstring dirmask;
+	int i;
+	completion_remote_t info = { "", NULL, 1, len, text, len };
+
+	if (len >= PATH_MAX)
+		return(NULL);
+
+	info.matches = (char **)malloc(sizeof(info.matches[0])*MAX_COMPLETIONS);
+	if (!info.matches) return NULL;
+	info.matches[0] = NULL;
+
+	for (i = len-1; i >= 0; i--)
+		if ((text[i] == '/') || (text[i] == '\\'))
+			break;
+	info.text = text+i+1;
+	info.samelen = info.len = len-i-1;
+
+	if (i > 0) {
+		strncpy(info.dirmask, text, i+1);
+		info.dirmask[i+1] = 0;
+		snprintf(dirmask, sizeof(dirmask), "%s%*s*", cur_dir, i-1, text);
+	} else
+		snprintf(dirmask, sizeof(dirmask), "%s*", cur_dir);
+
+	if (cli_list(cli, dirmask, aDIR | aSYSTEM | aHIDDEN, completion_remote_filter, &info) < 0)
+		goto cleanup;
+
+	if (info.count == 2)
+		info.matches[0] = strdup(info.matches[1]);
+	else {
+		info.matches[0] = malloc(info.samelen+1);
+		if (!info.matches[0])
+			goto cleanup;
+		strncpy(info.matches[0], info.matches[1], info.samelen);
+		info.matches[0][info.samelen] = 0;
+	}
+	info.matches[info.count] = NULL;
+	return info.matches;
+
+ cleanup:
+	for (i = 0; i < info.count; i++)
+		free(info.matches[i]);
+	free(info.matches);
+	return NULL;
+}
+
+static char **completion_fn(const char *_text, int start, int end)
+{
+	rl_completion_append_character = ' ';
+
+	if (start == 0)
+		return(completion_commands(_text, end));
+	else {
+		const char *text, *sp;
+		int cmd;
+		char compl_type;
+
+		text = rl_line_buffer;
+		sp = strchr(text, ' ');
+
+		if (sp == NULL)
+			return(NULL);
+		for (cmd = 0; commands[cmd].name; cmd++)
+			if ((strncmp(commands[cmd].name, text, sp-text) == 0) && (commands[cmd].name[sp-text] == 0))
+				break;
+		if (commands[cmd].name == NULL)
+			return(NULL);
+
+		while (*sp == ' ')
+			sp++;
+
+		if (sp == (text+start))
+			compl_type = commands[cmd].compl_args[0];
+		else
+			compl_type = commands[cmd].compl_args[1];
+
+		if (compl_type == COMPL_REMOTE)
+			return(completion_remote(_text, end-start));
+		else /* fall back to local filename completion */
+			return(NULL);
+	}
+}
+#else /* HAVE_LIBREADLINE */
+static char **completion_fn(const char *_text, int start, int end)
+{
+	return(NULL);
+}
+#endif
 
 /****************************************************************************
 make sure we swallow keepalives during idle time


More information about the samba-technical mailing list