Lists available completions when a user presses tab from a telnet client.  If
there is only one completion, it automatically does it.  This supports commands,
sub-commands, files, and directories.

This also introduces a change to the command helpers that allows external files
to check if a command can be run.  This helps to control what possible command
completions are available.
---
 src/helper/Makefile.am     |    6 +-
 src/helper/command.c       |    2 +-
 src/helper/command.h       |    2 +
 src/helper/completion.c    |  186 ++++++++++++++++++++++++++++++++++++++++++++
 src/helper/completion.h    |   29 +++++++
 src/server/telnet_server.c |   73 +++++++++++++++++
 6 files changed, 295 insertions(+), 3 deletions(-)
 create mode 100644 src/helper/completion.c
 create mode 100644 src/helper/completion.h

diff --git a/src/helper/Makefile.am b/src/helper/Makefile.am
index 3ec4f31..367fcf8 100644
--- a/src/helper/Makefile.am
+++ b/src/helper/Makefile.am
@@ -23,7 +23,8 @@ libhelper_la_SOURCES = \
        time_support.c \
        replacements.c \
        fileio.c \
-       membuf.c
+       membuf.c \
+       completion.c
 
 if IOUTIL
 libhelper_la_SOURCES += ioutil.c
@@ -51,7 +52,8 @@ noinst_HEADERS = \
        jim.h \
        jim-eventloop.h \
        system.h \
-       bin2char.c
+       bin2char.c \
+       completion.h
 
 EXTRA_DIST = startup.tcl
 
diff --git a/src/helper/command.c b/src/helper/command.c
index b4e31ea..622af7f 100644
--- a/src/helper/command.c
+++ b/src/helper/command.c
@@ -555,7 +555,7 @@ char *command_name(struct command *c, char delim)
        return __command_name(c, delim, 0);
 }
 
-static bool command_can_run(struct command_context *cmd_ctx, struct command *c)
+bool command_can_run(struct command_context *cmd_ctx, struct command *c)
 {
        return c->mode == COMMAND_ANY || c->mode == cmd_ctx->mode;
 }
diff --git a/src/helper/command.h b/src/helper/command.h
index 8a418d3..d8e0e89 100644
--- a/src/helper/command.h
+++ b/src/helper/command.h
@@ -290,6 +290,8 @@ int unregister_command(struct command_context *cmd_ctx,
 int unregister_all_commands(struct command_context *cmd_ctx,
                struct command *parent);
 
+bool command_can_run(struct command_context *cmd_ctx, struct command *name);
+
 struct command *command_find_in_context(struct command_context *cmd_ctx,
                const char *name);
 struct command *command_find_in_parent(struct command *parent,
diff --git a/src/helper/completion.c b/src/helper/completion.c
new file mode 100644
index 0000000..c3e2254
--- /dev/null
+++ b/src/helper/completion.c
@@ -0,0 +1,186 @@
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <dirent.h>
+
+#include <helper/command.h>
+#include "completion.h"
+
+void complete_add_match(struct completion_match **comp, const char *search,
+               const char *match, enum completion_type type)
+{
+       struct completion_match *new = malloc(sizeof(struct completion_match));
+       struct completion_match *top = *comp;
+       new->value = strdup(match);
+       new->new = new->value + strlen(search);
+       new->type = type;
+       if (!top) {
+               *comp = malloc(sizeof(struct completion_match));
+               top = *comp;
+               top->new = NULL;
+               top->next = top;
+               top->prev = top;
+               top->type = COMPLETION_NIL;
+               top->value = NULL;
+       }
+
+       new->prev = top->prev;
+       top->prev->next = new;
+       new->next = top;
+       top->prev = new;
+}
+
+struct completion_match *complete_command(struct command_context *cmd_ctx,
+               const char *comp)
+{
+       struct completion_match *retval = NULL;
+       char *copy = strdup(comp);
+       char *tok = NULL, *p = NULL;
+
+       tok = strtok_r(copy, " ", &p);
+       struct command *prev = cmd_ctx->commands;
+       struct command *cmd = command_find_in_context(cmd_ctx, tok);
+       while (cmd) {
+               tok = strtok_r(NULL, " ", &p);
+               if (tok) {
+                       if (cmd->children) {
+                               prev = cmd->children;
+                               cmd = command_find_in_parent(cmd, tok);
+                       } else {
+                               break;
+                       }
+               } else {
+                       if (comp[strlen(comp) - 1] == ' ') {
+                               if (cmd->children) {
+                                       prev = cmd->children;
+                                       tok = "";
+                               }
+                       } else {
+                               complete_add_match(&retval, cmd->name, 
cmd->name, COMPLETION_COMMAND);
+                       }
+
+                       break;
+               }
+       }
+
+       if (tok) {
+               struct command *c = NULL;
+               int tok_len = strlen(tok);
+               for (c = prev; c; c = c->next) {
+                       if (!command_can_run(cmd_ctx, c))
+                               continue;
+
+                       if (strncmp(c->name, tok, tok_len) == 0)
+                               complete_add_match(&retval, tok, c->name, 
COMPLETION_COMMAND);
+               }
+       }
+
+       free(copy);
+
+       return retval;
+}
+
+struct completion_match *complete_file(const char *comp)
+{
+       struct completion_match *retval = NULL;
+       // try to find start of match at last space
+       const char *match = strrchr(comp, ' ');
+       if (NULL == match) // no spaces, so use whole string
+               match = comp;
+       else // advance past space
+               match += 1;
+
+       // last / splits between directory and file to match
+       const char *temp = strrchr(match, '/');
+       char *dir_loc = NULL;
+       bool alloc_dir = false;
+       if (NULL == temp) {
+               // no slash, so assume current directory
+               dir_loc = "./";
+       } else {
+               // substring out the directory and move match to slash location
+               int size = temp - match + 1;
+               dir_loc = calloc(size + 1, 1);
+               alloc_dir = true;
+               strncpy(dir_loc, match, size);
+               match = temp + 1;
+       }
+
+       DIR *dp = opendir(dir_loc);
+       struct dirent *ep = NULL;
+       struct stat x;
+       char *full_path = NULL;
+       int match_len = strlen(match);
+       int dir_len = strlen(dir_loc);
+       if (dp) {
+               ep = readdir(dp);
+               while (ep) {
+                       if (strncmp(ep->d_name, match, match_len) == 0) {
+                               full_path = calloc(dir_len + strlen(ep->d_name) 
+ 1, 1);
+                               strcpy(full_path, dir_loc);
+                               strcat(full_path, ep->d_name);
+                               lstat(full_path, &x);
+                               free(full_path);
+
+                               enum completion_type type = 
(S_ISDIR(x.st_mode)) ? COMPLETION_DIR : COMPLETION_FILE;
+                               complete_add_match(&retval, match, ep->d_name, 
type);
+                       }
+
+                       ep = readdir(dp);
+               }
+       }
+
+       // free up used memory
+       if (alloc_dir)
+               free(dir_loc);
+
+       return retval;
+}
+
+void complete_clear(struct completion_match *matches)
+{
+       struct completion_match *cur = matches->next;
+       struct completion_match *next = NULL;
+       while (cur->value) {
+               next = cur->next;
+               free(cur->value);
+               free(cur);
+               cur = next;
+       }
+
+       free(matches);
+}
+
+void complete_combine(struct completion_match **dest, struct completion_match 
*more)
+{
+       struct completion_match *top = *dest;
+       if (!top) {
+               *dest = more;
+       } else if (more) {
+               more->prev->next = top;
+               more->next->prev = top->prev->next;
+               top->prev->next = more->next;
+               top->prev = more->prev;
+               more->prev = more;
+               more->next = more;
+               complete_clear(more);
+       }
+}
+
+bool complete_single(struct completion_match *matches)
+{
+       if (matches->next->next->value)
+               return false;
+
+       return true;
+}
+
+struct completion_match *complete(struct command_context *cmd_ctx, const char 
*comp)
+{
+       struct completion_match *retval = complete_command(cmd_ctx, comp);
+       struct completion_match *more = complete_file(comp);
+       complete_combine(&retval, more);
+
+       return retval;
+}
diff --git a/src/helper/completion.h b/src/helper/completion.h
new file mode 100644
index 0000000..0266a47
--- /dev/null
+++ b/src/helper/completion.h
@@ -0,0 +1,29 @@
+#ifndef _COMPLETION_H
+#define _COMPLETION_H
+
+#include <helper/types.h>
+
+enum completion_type {
+       COMPLETION_NIL,
+       COMPLETION_COMMAND,
+       COMPLETION_DIR,
+       COMPLETION_FILE,
+};
+
+struct completion_match {
+       char *value;
+       char *new;
+       enum completion_type type;
+       struct completion_match *next;
+       struct completion_match *prev;
+};
+
+struct completion_match *complete_command(struct command_context *cmd_ctx,
+               const char *comp);
+struct completion_match *complete_file(const char *comp);
+
+struct completion_match *complete(struct command_context *cmd_ctx, const char 
*comp);
+void complete_clear(struct completion_match *matches);
+bool complete_single(struct completion_match *matches);
+
+#endif /* _COMPLETION_H */
diff --git a/src/server/telnet_server.c b/src/server/telnet_server.c
index f42f056..a385145 100644
--- a/src/server/telnet_server.c
+++ b/src/server/telnet_server.c
@@ -29,6 +29,8 @@
 
 #include "telnet_server.h"
 #include <target/target_request.h>
+#include <helper/completion.h>
+#include <dirent.h>
 
 static unsigned short telnet_port = 4444;
 
@@ -192,6 +194,8 @@ void telnet_clear_line(struct connection *connection, 
struct telnet_connection *
        t_con->line_cursor = 0;
 }
 
+
+
 int telnet_input(struct connection *connection)
 {
        int bytes_read;
@@ -385,6 +389,75 @@ int telnet_input(struct connection *connection)
                                                        }
                                                        t_con->state = 
TELNET_STATE_DATA;
                                                }
+                                               else if (*buf_p == 0x09) /* tab 
*/
+                                               {
+                                                       /* grab a substring 
base on where the cursor is */
+                                                       char *sub = 
calloc(t_con->line_cursor + 1, 1);
+                                                       strncpy(sub, 
t_con->line, t_con->line_cursor);
+                                                       struct completion_match 
*matches = complete(command_context, sub);
+                                                       if (matches) {
+                                                               if 
(complete_single(matches)) {
+                                                                       // only 
one match, so we add the new text
+                                                                       matches 
= matches->next;
+                                                                       int 
is_dir = 0;
+                                                                       int 
additional = 0;
+                                                                       if 
(matches->type == COMPLETION_DIR) {
+                                                                               
// add / to directory completions
+                                                                               
is_dir = 1;
+                                                                               
if (t_con->line[t_con->line_cursor] != '/')
+                                                                               
        additional = 1;
+                                                                       } else 
if (t_con->line_size == t_con->line_cursor)
+                                                                               
additional = 1;
+
+                                                                       int 
new_len = strlen(matches->new);
+                                                                       
t_con->line[t_con->line_size] = '\0';
+
+                                                                       // move 
existing text to the right, making
+                                                                       // room 
for completion text
+                                                                       
memmove(t_con->line + t_con->line_cursor + new_len + additional,
+                                                                               
        t_con->line + t_con->line_cursor,
+                                                                               
        t_con->line_size - t_con->line_cursor + 1);
+
+                                                                       // copy 
in the new stuff
+                                                                       
strncpy(t_con->line + t_con->line_cursor, matches->new, new_len);
+                                                                       
t_con->line_cursor += new_len;
+                                                                       if 
(is_dir && additional) {
+                                                                               
// add directory slash
+                                                                               
t_con->line[t_con->line_cursor++] = '/';
+                                                                               
if (t_con->line_cursor >= t_con->line_size)
+                                                                               
        t_con->line[t_con->line_cursor] = '\0';
+                                                                       } else 
if (additional) {
+                                                                               
// add extra space
+                                                                               
t_con->line[t_con->line_cursor++] = ' ';
+                                                                               
if (t_con->line_cursor >= t_con->line_size)
+                                                                               
        t_con->line[t_con->line_cursor] = '\0';
+                                                                       }
+
+                                                                       
t_con->line_size = strlen(t_con->line);
+                                                               } else {
+                                                                       struct 
completion_match *p = NULL;
+                                                                       
telnet_write(connection, "\n\r", 2);
+                                                                       for (p 
= matches->next; p && p->value; p = p->next) {
+                                                                               
telnet_outputline(connection, p->value);
+                                                                               
if (p->type == COMPLETION_DIR)
+                                                                               
        telnet_outputline(connection, "/");
+
+                                                                               
telnet_write(connection, "\n\r", 2);
+                                                                       }
+                                                               }
+
+                                                               
complete_clear(matches);
+                                                       }
+
+                                                       /* put the command line 
to its previous state */
+                                                       
telnet_prompt(connection);
+                                                       
telnet_write(connection, t_con->line, t_con->line_size);
+                                                       int i;
+                                                       for (i = 
t_con->line_size; i > t_con->line_cursor; i--)
+                                                               
telnet_write(connection, "\b", 1);
+
+                                                       free(sub);
+                                               }
                                                else
                                                {
                                                        LOG_DEBUG("unhandled 
nonprintable: %2.2x", *buf_p);
-- 
1.6.5.2

_______________________________________________
Openocd-development mailing list
[email protected]
https://lists.berlios.de/mailman/listinfo/openocd-development

Reply via email to