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