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