sftp tab completion patch (First release - NOT FOR INCLUDING YET)

Ben Lindstrom mouring at eviladmin.org
Sat Apr 1 17:32:48 EST 2006


This applies to the OpenBSD --current tree.  Don't see why it shouldn't 
work under portable.

Within the patch are the updates I need to make before I'll actually 
submit it for real, but I figured I'd make a public drop.

Since I'm not on this list anymore keep me in the CC: if you want me to 
respond.

I'll continue to work as time permits, but it would be nice if people who 
want this in step up and give feedback.

- Ben


Index: sftp.c
===================================================================
RCS file: /cvs/src/usr.bin/ssh/sftp.c,v
retrieving revision 1.80
diff -u -r1.80 sftp.c
--- sftp.c	27 Mar 2006 23:15:46 -0000	1.80
+++ sftp.c	31 Mar 2006 23:31:13 -0000
@@ -64,6 +64,9 @@
  int remote_glob(struct sftp_conn *, const char *, int,
      int (*)(const char *, int), glob_t *); /* proto for sftp-glob.c */

+/* sftp connection structure */
+struct sftp_conn *conn;
+
  /* Separators for interactive commands */
  #define WHITESPACE " \t\r\n"

@@ -108,42 +111,49 @@
  struct CMD {
  	const char *c;
  	const int n;
+	const int t;
  };

+/* Type of completion */
+#define NOARGS 		0
+#define REMOTE 		1
+#define LOCAL 		2
+
+
  static const struct CMD cmds[] = {
-	{ "bye",	I_QUIT },
-	{ "cd",		I_CHDIR },
-	{ "chdir",	I_CHDIR },
-	{ "chgrp",	I_CHGRP },
-	{ "chmod",	I_CHMOD },
-	{ "chown",	I_CHOWN },
-	{ "dir",	I_LS },
-	{ "exit",	I_QUIT },
-	{ "get",	I_GET },
-	{ "mget",	I_GET },
-	{ "help",	I_HELP },
-	{ "lcd",	I_LCHDIR },
-	{ "lchdir",	I_LCHDIR },
-	{ "lls",	I_LLS },
-	{ "lmkdir",	I_LMKDIR },
-	{ "ln",		I_SYMLINK },
-	{ "lpwd",	I_LPWD },
-	{ "ls",		I_LS },
-	{ "lumask",	I_LUMASK },
-	{ "mkdir",	I_MKDIR },
-	{ "progress",	I_PROGRESS },
-	{ "put",	I_PUT },
-	{ "mput",	I_PUT },
-	{ "pwd",	I_PWD },
-	{ "quit",	I_QUIT },
-	{ "rename",	I_RENAME },
-	{ "rm",		I_RM },
-	{ "rmdir",	I_RMDIR },
-	{ "symlink",	I_SYMLINK },
-	{ "version",	I_VERSION },
-	{ "!",		I_SHELL },
-	{ "?",		I_HELP },
-	{ NULL,			-1}
+	{ "bye",	I_QUIT, 	NOARGS },
+	{ "cd",		I_CHDIR,	REMOTE },
+	{ "chdir",	I_CHDIR,	REMOTE },
+	{ "chgrp",	I_CHGRP,	REMOTE },
+	{ "chmod",	I_CHMOD,	REMOTE },
+	{ "chown",	I_CHOWN,	REMOTE },
+	{ "dir",	I_LS,		REMOTE },
+	{ "exit",	I_QUIT,		NOARGS },
+	{ "get",	I_GET,		REMOTE },
+	{ "mget",	I_GET,		REMOTE },
+	{ "help",	I_HELP,		NOARGS },
+	{ "lcd",	I_LCHDIR,	LOCAL },
+	{ "lchdir",	I_LCHDIR,	LOCAL },
+	{ "lls",	I_LLS,		LOCAL },
+	{ "lmkdir",	I_LMKDIR,	LOCAL  },
+	{ "ln",		I_SYMLINK,	REMOTE },
+	{ "lpwd",	I_LPWD,		LOCAL },
+	{ "ls",		I_LS, 		REMOTE },
+	{ "lumask",	I_LUMASK,	NOARGS },
+	{ "mkdir",	I_MKDIR,	REMOTE },
+	{ "progress",	I_PROGRESS,	NOARGS },
+	{ "put",	I_PUT,		LOCAL },
+	{ "mput",	I_PUT,		LOCAL },
+	{ "pwd",	I_PWD,		REMOTE },
+	{ "quit",	I_QUIT,		NOARGS },
+	{ "rename",	I_RENAME,	REMOTE },
+	{ "rm",		I_RM,		REMOTE },
+	{ "rmdir",	I_RMDIR,	REMOTE },
+	{ "symlink",	I_SYMLINK,	REMOTE },
+	{ "version",	I_VERSION,	NOARGS },
+	{ "!",		I_SHELL,	NOARGS },
+	{ "?",		I_HELP,		NOARGS },
+	{ NULL,		-1,		-1}
  };

  int interactive_loop(int fd_in, int fd_out, char *file1, char *file2);
@@ -1231,13 +1241,194 @@
  	return ("sftp> ");
  }

+/* 
+ * Before Asking to be included:
+ * XXX Support Quoting in file completion
+ * XXX Fix MAXARG to be more dynamic
+ * XXX Better column display of multi-matches
+ * XXX Kill the need for MAXLIST limit [if I don't will be yelled at?]
+ * XXX Side by side comparision of ftp and tweak the code to better mimicing
+ * XXX Ask for $50 per user of this patch so I can give up my day job
+ * XXX Ask for Nate's (from undeadly.org fourm) head for making me "rue the day"
+ * XXX Wait for Theo to rip me a new one due to the code below
+ * XXX Sit on the patch for another year and rewrite using libunedit 2.0
+ */
+char *
+complete_ambiguous(char *word,  char **list, size_t count)
+{
+	if (count > 1) {
+		char *match = list[0];
+		size_t y, matchlen = strlen(match);
+
+		printf("\n");
+		printf("%s\t", list[0]);
+		for (y = 1; list[y]; y++) {
+			int x;
+
+			printf("%s\t", list[y]);
+			for (x = 0; x < matchlen; x++) 
+				if (match[x] != list[y][x]) 
+					break;
+
+			matchlen = x;
+		}
+		printf("\n");
+		if (matchlen > strlen(word)) {
+			char *tmp = xstrdup(list[0]);
+
+			tmp[matchlen] = NULL;
+			return (tmp);
+		}
+	} else 
+		return (xstrdup(list[0]));
+
+	return (NULL);
+}
+
+
+int
+complete_cmd_parse(EditLine *el, char *cmd)
+{
+	int y, count = 0;
+	#define MAXLIST 20
+	char *list[MAXLIST], *tmp;
+	int cmdlen = strlen(cmd);
+
+	for (y = 0; cmds[y].c; y++)  {
+		if (!strncasecmp(cmd, cmds[y].c, cmdlen)) {
+			list[count++] = xstrdup(cmds[y].c);
+			if (count >= MAXLIST)
+				fatal("Exceeded MAXLIST.");
+		}
+		list[count] = NULL;
+	}
+
+	if (count > 0) {
+		tmp = complete_ambiguous(cmd, list, count);
+		for (y = 1; list[y]; y++) 
+			xfree(list[y]);
+
+		if (tmp != NULL && strlen(tmp) > strlen(cmd)) {
+			if (el_insertstr(el, tmp + strlen(cmd)) == -1)
+				fatal("el_insertstr failed.");
+			xfree(tmp);
+		}
+	}
+
+	return (count);
+}
+
+int
+complete_is_remote(char *cmd) {
+	int i;
+
+	for (i = 0; cmds[i].c; i++) {
+		size_t cmdlen = strlen(cmds[i].c);
+
+		if (!strncasecmp(cmd, cmds[i].c, cmdlen)) 
+			return cmds[i].t;
+	}
+
+	return (-1);
+}
+
+int
+complete_match(EditLine *el, char *file, int remote)
+{
+	glob_t g;
+	size_t len = strlen(file) + 2;
+	char *tmp = xmalloc(len);
+
+	memset(&g, 0, sizeof(g));
+	snprintf(tmp, len, "%s*",  file);
+	if (remote == LOCAL) 
+		glob(tmp, GLOB_DOOFFS, NULL, &g);
+	else
+		remote_glob(conn, tmp, 0, NULL, &g);
+
+	xfree(tmp);
+
+	if (g.gl_matchc == 0) 
+		return (0);
+
+	tmp = complete_ambiguous(file, g.gl_pathv, g.gl_matchc);
+	globfree(&g);
+	if (tmp != NULL && strlen(tmp) > strlen(file)) {
+		if (el_insertstr(el, tmp + strlen(file)) == -1)
+			fatal("el_insertstr failed.");
+		xfree(tmp);
+	}
+	return (g.gl_matchc);
+}
+
+unsigned char
+complete(EditLine *el, int ch)
+{
+	const LineInfo *lf;
+	size_t len, pos;
+	char *line;
+	int argc = 0, ap_loc = 0, ap_cur = 0;
+	#define MAXARG 99
+	char **ap, *argv[MAXARG];
+
+	ch = ch;                /* not used */
+	lf = el_line(el);
+	len = lf->lastchar - lf->buffer + 1;
+	line = (char *)malloc(len);
+	strlcpy(line, lf->buffer, len);
+	pos = len - (lf->lastchar - lf->cursor);
+
+	/* build an array of items */
+	for (ap = argv; ap < &argv[MAXARG - 1] &&
+	    (*ap = strsep(&line, " ")) != NULL; ) {
+		if (**ap != '\0') {
+			int ap_new;
+
+			ap_new = ap_loc + strlen(*ap);
+			if (ap_new >= pos && ap_loc <= pos)
+				ap_cur = argc; 
+			ap_loc = ap_new;
+			ap++;
+			argc++;
+		}
+	}
+	*ap = NULL;
+
+	if (argc == 0)
+		return(CC_ERROR);
+
+	if (pos == len)
+		ap_cur = argc - 1;
+
+	/* Complete Stuff */
+	if (ap_cur == 0) {				/* Command Match */ 
+		if (complete_cmd_parse(el, argv[0]) == 0) 
+			return (CC_ERROR);
+
+		return (CC_REDISPLAY);
+	} else {					/* File Matching */
+		int remote = complete_is_remote(argv[0]);
+
+		if (remote != 0) {
+			if (complete_match(el, argv[ap_cur], remote) == 0)
+				return (CC_ERROR);
+
+			return (CC_REDISPLAY);
+		} 
+
+		return (CC_ERROR);
+	}
+
+	/* Clean up */
+	xfree(line);
+}
+
  int
  interactive_loop(int fd_in, int fd_out, char *file1, char *file2)
  {
  	char *pwd;
  	char *dir = NULL;
  	char cmd[2048];
-	struct sftp_conn *conn;
  	int err, interactive;
  	EditLine *el = NULL;
  	History *hl = NULL;
@@ -1257,6 +1448,11 @@
  		el_set(el, EL_TERMINAL, NULL);
  		el_set(el, EL_SIGNAL, 1);
  		el_source(el, NULL);
+
+		/* Tab Completion */
+		el_set(el, EL_ADDFN, "ftp-complete", 
+		    "Context senstive argument completion", complete);
+		el_set(el, EL_BIND, "^I", "ftp-complete", NULL);
  	}

  	conn = do_init(fd_in, fd_out, copy_buffer_len, num_requests);




More information about the openssh-unix-dev mailing list