diff --git a/urjtag/ChangeLog b/urjtag/ChangeLog index 028a2346..97c7e39c 100644 --- a/urjtag/ChangeLog +++ b/urjtag/ChangeLog @@ -7,6 +7,16 @@ of urj_parse_line and into its own function urj_tokenize_lines. This lets us reuse this code elsewhere. + * include/urjtag/cmd.h, src/apps/jtag/jtag.c, src/cmd/cmd.h, + src/cmd/cmd_cmd.c: Rewrite command completion so that sub-options to + commands may be completed as well. Those commands must implement their + own sub-completion handlers in order to support this. + * src/cmd/cmd_bfin.c, src/cmd/cmd_cable.c, src/cmd/cmd_get.c, + src/cmd/cmd_help.c, src/cmd/cmd_include.c, src/cmd/cmd_initbus.c, + src/cmd/cmd_instruction.c, src/cmd/cmd_salias.c, src/cmd/cmd_set.c, + src/cmd/cmd_shift.c, src/cmd/cmd_signal.c, src/cmd/cmd_test.c: Implement + sub-completion handlers for these commands. + 2011-02-18 Mike Frysinger * src/bfin/bfin-part.c (_bfin_part_init): Add missing "void" to param list. diff --git a/urjtag/include/urjtag/cmd.h b/urjtag/include/urjtag/cmd.h index 849313cc..b7ea807e 100644 --- a/urjtag/include/urjtag/cmd.h +++ b/urjtag/include/urjtag/cmd.h @@ -42,18 +42,18 @@ * handled in the same way, urj_error is set to #URJ_ERROR_SYNTAX. */ int urj_cmd_run (urj_chain_t *chain, char *params[]); + /** - * Search through registered commands + * Attempt completion of part of a command string * - * @param text match commands whose prefix equals text. Rotates - * through the registered commands. The prefix length is set when - * the rotating state is reset. - * @@@@ RFHH that is weird behaviour. Why not do the prefix length as strlen(text)? - * @param state if 0, reset the rotating state to start from the beginning + * @param chain chain to possibly use for some completions + * @param line full (incomplete) command line + * @param point current cursor position in the line * - * @return malloc'ed value. The caller is responsible for freeing it. - * NULL for malloc failure or end of command list. + * @return malloc'ed array of strings. The caller is responsible for freeing + * all of them, and the array itself. NULL for malloc failure or end of + * possible completions. */ -char *urj_cmd_find_next (const char *text, int state); +char **urj_cmd_complete (urj_chain_t *chain, const char *line, int point); #endif /* URJ_CMD_H */ diff --git a/urjtag/src/apps/jtag/jtag.c b/urjtag/src/apps/jtag/jtag.c index 30751de2..af60cc99 100644 --- a/urjtag/src/apps/jtag/jtag.c +++ b/urjtag/src/apps/jtag/jtag.c @@ -152,13 +152,31 @@ jtag_create_jtagdir (void) #ifdef HAVE_LIBREADLINE #ifdef HAVE_READLINE_COMPLETION -static char ** -urj_cmd_completion (const char *text, int start, int end) +static urj_chain_t *active_chain; + +static char * +urj_cmd_completion (const char *text, int matches) { - char **ret = NULL; + /* Cache the current set of matches */ + static char **all_matches = NULL; + static int idx; + char *ret = NULL; + + if (matches == 0) + { /* Build new list of completions */ + free (all_matches); + all_matches = NULL; + idx = 0; + + all_matches = urj_cmd_complete (active_chain, rl_line_buffer, rl_point); + } - if (start == 0) - ret = rl_completion_matches (text, urj_cmd_find_next); + if (all_matches) + { + ret = all_matches[idx]; + if (ret) + ++idx; + } return ret; } @@ -544,10 +562,12 @@ main (int argc, char *const argv[]) #ifdef HAVE_LIBREADLINE #ifdef HAVE_READLINE_COMPLETION + active_chain = chain; + rl_readline_name = "urjtag"; rl_completer_quote_characters = "\""; rl_filename_completion_desired = 1; rl_filename_quote_characters = " "; - rl_attempted_completion_function = urj_cmd_completion; + rl_completion_entry_function = urj_cmd_completion; #endif #endif diff --git a/urjtag/src/cmd/cmd.h b/urjtag/src/cmd/cmd.h index 9a63daf6..1a92a153 100644 --- a/urjtag/src/cmd/cmd.h +++ b/urjtag/src/cmd/cmd.h @@ -47,6 +47,8 @@ typedef struct /** @return URJ_STATUS_OK on success; URJ_STATUS_FAIL on error, both * syntax and library errors */ int (*run) (urj_chain_t *chain, char *params[]); + void (*complete) (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point); } urj_cmd_t; #define _URJ_CMD(cmd) extern const urj_cmd_t urj_cmd_##cmd; @@ -87,4 +89,51 @@ do { \ arr[i]->name, _(arr[i]->description)); \ } while (0) +/** + * Internal completion helper for adding to the set of matches. + * + * @param match this must be malloced memory that may be freed in the + * future by common code -- do not free it yourself + */ +void urj_completion_add_match (char ***matches, size_t *cnt, char *match); + +/** + * Internal completion helper for adding to the set of matches. + * + * @param match this string will be strduped before being passed down + * to the urj_completion_add_match helper. + */ +void urj_completion_add_match_dupe (char ***matches, size_t *cnt, + const char *match); + +/** + * Internal completion helper for possibly adding to the set of matches. + * If text matches the leading portion of match, then it will be added. + * + * @param text the string to compare to match (e.g. user input) + * @param match the string to possibly add to the set of matches + */ +void urj_completion_maybe_add_match (char ***matches, size_t *cnt, + const char *text, const char *match); + +/** + * This is just like urj_completion_maybe_add_match, except the length of + * text is precomputed. This is so common, we get a dedicated function. + * + * @param text the string to compare to match (e.g. user input) + * @param text_len the length of text + * @param match the string to possibly add to the set of matches + */ +void urj_completion_mayben_add_match (char ***matches, size_t *cnt, + const char *text, size_t text_len, + const char *match); + +/** + * Internal completion helper for matching against the signal list. + * Since many functions involve signals as an option, unify the code + * in one place. + */ +void cmd_signal_complete (urj_chain_t *chain, char ***matches, + size_t *match_cnt, const char *text, size_t text_len); + #endif /* URJ_CMD_H */ diff --git a/urjtag/src/cmd/cmd_bfin.c b/urjtag/src/cmd/cmd_bfin.c index f5cb15bc..acec61b9 100644 --- a/urjtag/src/cmd/cmd_bfin.c +++ b/urjtag/src/cmd/cmd_bfin.c @@ -499,9 +499,22 @@ cmd_bfin_help (void) "bfin", "bfin", "bfin" ); } +static void +cmd_bfin_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + if (token_point != 1) + return; + + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "execute"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "emulation"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "reset"); +} + const urj_cmd_t urj_cmd_bfin = { "bfin", N_("Blackfin specific commands"), cmd_bfin_help, - cmd_bfin_run + cmd_bfin_run, + cmd_bfin_complete, }; diff --git a/urjtag/src/cmd/cmd_cable.c b/urjtag/src/cmd/cmd_cable.c index 49366fd7..3fd5700b 100644 --- a/urjtag/src/cmd/cmd_cable.c +++ b/urjtag/src/cmd/cmd_cable.c @@ -194,9 +194,32 @@ cmd_cable_help (void) urj_cmd_show_list (urj_tap_cable_drivers); } +static void +cmd_cable_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + size_t i; + + switch (token_point) + { + case 1: + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "probe"); + + for (i = 0; urj_tap_cable_drivers[i]; i++) + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, + urj_tap_cable_drivers[i]->name); + break; + case 2: + /* XXX: in the future, we want to complete cable options too */ + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "help"); + break; + } +} + const urj_cmd_t urj_cmd_cable = { "cable", N_("select JTAG cable"), cmd_cable_help, - cmd_cable_run + cmd_cable_run, + cmd_cable_complete, }; diff --git a/urjtag/src/cmd/cmd_cmd.c b/urjtag/src/cmd/cmd_cmd.c index 0fe09540..de4eb061 100644 --- a/urjtag/src/cmd/cmd_cmd.c +++ b/urjtag/src/cmd/cmd_cmd.c @@ -26,10 +26,11 @@ #include #include +#include #include #include - +#include #include #include "cmd.h" @@ -42,36 +43,130 @@ const urj_cmd_t * const urj_cmds[] = { /* * @param text match commands whose prefix equals text. Rotates - * through the registered commands. The prefix length is set when - * the rotating state is reset. - * @@@@ RFHH that is weird behaviour. Why not do the prefix length as strlen(text)? + * through the registered commands. The prefix length is set when the + * rotating state is reset. This is the behavior as dictated by readline. */ -char * -urj_cmd_find_next (const char *text, int state) +static const urj_cmd_t * +urj_cmd_find (const char *text, ssize_t last_idx) { - static size_t cmd_idx, len; - char *next = NULL; + static size_t len; + const urj_cmd_t *ret; - if (!state) - { - cmd_idx = 0; + if (last_idx == -1) len = strlen (text); + + while (urj_cmds[++last_idx]) + { + ret = urj_cmds[last_idx]; + if (!strncmp (ret->name, text, len)) + return ret; } - while (urj_cmds[cmd_idx]) + return NULL; +} + +/* These three funcs are meant to be used by sub-command completers */ +void +urj_completion_add_match (char ***matches, size_t *cnt, char *match) +{ + *matches = realloc (*matches, sizeof (**matches) * (*cnt + 2)); + (*matches)[(*cnt)++] = match; +} + +void +urj_completion_add_match_dupe (char ***matches, size_t *cnt, const char *match) +{ + urj_completion_add_match (matches, cnt, strdup (match)); +} + +void +urj_completion_mayben_add_match (char ***matches, size_t *cnt, const char *text, + size_t text_len, const char *match) +{ + if (!strncmp (text, match, text_len)) + urj_completion_add_match_dupe (matches, cnt, match); +} + +void +urj_completion_maybe_add_match (char ***matches, size_t *cnt, const char *text, + const char *match) +{ + urj_completion_mayben_add_match (matches, cnt, text, strlen (text), match); +} + +static size_t +urt_completion_find_token_point (const char *line, int point) +{ + const char *cs = line; + size_t token_point = 0; + + /* Skip all leading whitespace first to make 2nd loop easier */ + while (isspace (*cs)) + ++cs; + + while (*cs) { - char *name = urj_cmds[cmd_idx++]->name; - if (!strncmp (name, text, len)) - { - next = strdup (name); - if (next == NULL) - urj_error_set (URJ_ERROR_OUT_OF_MEMORY, "strdup(%s) fails", - name); + if (point <= (cs - line)) break; + + ++cs; + + if (isspace (*cs)) + { + ++token_point; + while (isspace (*cs)) + ++cs; } } - return next; + return token_point; +} + +char ** +urj_cmd_complete (urj_chain_t *chain, const char *line, int point) +{ + char **tokens, **ret; + size_t token_cnt, token_point, ret_cnt; + const urj_cmd_t *cmd; + const char *name; + + /* Split up the current line to make completion easier */ + if (urj_tokenize_line (line, &tokens, &token_cnt)) + return NULL; + if (token_cnt == 0) + name = ""; + else + name = tokens[0]; + + ret = NULL; + ret_cnt = 0; + + /* Figure out which token we're pointing to */ + token_point = urt_completion_find_token_point (line, point); + + /* Are we completing the command itself ? Re-use the 'help' ... */ + if (token_point == 0) + name = "help"; + + /* Figure out options for which command we want to complete */ + cmd = urj_cmd_find (name, -1); + if (cmd && cmd->complete) + { + if (token_cnt) + name = tokens[token_point] ? : ""; + else + name = ""; + + cmd->complete (chain, &ret, &ret_cnt, name, strlen (name), token_point); + + if (ret_cnt) + ret[ret_cnt] = NULL; + } + + if (token_cnt) + urj_tokens_free (tokens); + + return ret; } int diff --git a/urjtag/src/cmd/cmd_get.c b/urjtag/src/cmd/cmd_get.c index 2863862c..7a524199 100644 --- a/urjtag/src/cmd/cmd_get.c +++ b/urjtag/src/cmd/cmd_get.c @@ -92,9 +92,20 @@ cmd_get_help (void) "get"); } +static void +cmd_get_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + if (token_point != 1) + return; + + cmd_signal_complete (chain, matches, match_cnt, text, text_len); +} + const urj_cmd_t urj_cmd_get = { "get", N_("get external signal value"), cmd_get_help, - cmd_get_run + cmd_get_run, + cmd_get_complete, }; diff --git a/urjtag/src/cmd/cmd_help.c b/urjtag/src/cmd/cmd_help.c index 117ddc4c..10818288 100644 --- a/urjtag/src/cmd/cmd_help.c +++ b/urjtag/src/cmd/cmd_help.c @@ -78,9 +78,25 @@ cmd_help_help (void) "help"); } +static void +cmd_help_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + size_t i; + + /* Completing the command itself will come here as token 0 */ + if (token_point > 1) + return; + + for (i = 0; urj_cmds[i]; ++i) + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, + urj_cmds[i]->name); +} + const urj_cmd_t urj_cmd_help = { "help", N_("display this help"), cmd_help_help, - cmd_help_run + cmd_help_run, + cmd_help_complete, }; diff --git a/urjtag/src/cmd/cmd_include.c b/urjtag/src/cmd/cmd_include.c index b3548775..e2f30431 100644 --- a/urjtag/src/cmd/cmd_include.c +++ b/urjtag/src/cmd/cmd_include.c @@ -29,6 +29,10 @@ #include #include +#ifdef HAVE_LIBREADLINE +#include +#endif + #include #include #include @@ -97,11 +101,55 @@ cmd_include_help (void) cmd_include_or_script_help ("include"); } +static void +cmd_include_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ +#ifdef HAVE_LIBREADLINE + int state; + size_t implicit_len; + char *match, *search_text; + + /* Use the search path if path isn't explicitly relative/absolute */ + if (text[0] != '/' && text[0] != '.') + { + const char *jtag_data_dir = urj_get_data_dir (); + implicit_len = strlen (jtag_data_dir) + 1; + + search_text = malloc (implicit_len + text_len + 1); + if (!search_text) + return; + + sprintf (search_text, "%s/%s", jtag_data_dir, text); + text = search_text; + text_len += implicit_len; + } + else + { + implicit_len = 0; + search_text = NULL; + } + + state = 0; + while (1) + { + match = rl_filename_completion_function (text, state++); + if (!match) + break; + urj_completion_add_match_dupe (matches, match_cnt, match + implicit_len); + free (match); + } + + free (search_text); +#endif +} + const urj_cmd_t urj_cmd_include = { "include", N_("include command sequence from external repository"), cmd_include_help, - cmd_include_run + cmd_include_run, + cmd_include_complete, }; static int diff --git a/urjtag/src/cmd/cmd_initbus.c b/urjtag/src/cmd/cmd_initbus.c index d35004c8..2c9f2669 100644 --- a/urjtag/src/cmd/cmd_initbus.c +++ b/urjtag/src/cmd/cmd_initbus.c @@ -101,9 +101,24 @@ cmd_initbus_help (void) urj_cmd_show_list (urj_bus_drivers); } +static void +cmd_initbus_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + size_t i; + + if (token_point != 1) + return; + + for (i = 0; urj_bus_drivers[i]; i++) + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, + urj_bus_drivers[i]->name); +} + const const urj_cmd_t urj_cmd_initbus = { "initbus", N_("initialize bus driver for active part"), cmd_initbus_help, - cmd_initbus_run + cmd_initbus_run, + cmd_initbus_complete, }; diff --git a/urjtag/src/cmd/cmd_instruction.c b/urjtag/src/cmd/cmd_instruction.c index 4c9b0427..77949c93 100644 --- a/urjtag/src/cmd/cmd_instruction.c +++ b/urjtag/src/cmd/cmd_instruction.c @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -110,9 +111,33 @@ cmd_instruction_help (void) "instruction", "instruction", "instruction"); } +static void +cmd_instruction_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + urj_part_t *part; + urj_part_instruction_t *i; + + if (token_point != 1) + return; + + part = urj_tap_chain_active_part (chain); + if (part == NULL) + return; + + i = part->instructions; + while (i) + { + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, + i->name); + i = i->next; + } +} + const urj_cmd_t urj_cmd_instruction = { "instruction", N_("change active instruction for a part or declare new instruction"), cmd_instruction_help, - cmd_instruction_run + cmd_instruction_run, + cmd_instruction_complete, }; diff --git a/urjtag/src/cmd/cmd_salias.c b/urjtag/src/cmd/cmd_salias.c index 274fbe43..637deed7 100644 --- a/urjtag/src/cmd/cmd_salias.c +++ b/urjtag/src/cmd/cmd_salias.c @@ -94,9 +94,20 @@ cmd_salias_help (void) "signal"); } +static void +cmd_salias_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + if (token_point != 2) + return; + + cmd_signal_complete (chain, matches, match_cnt, text, text_len); +} + const urj_cmd_t urj_cmd_salias = { "salias", N_("define an alias for a signal"), cmd_salias_help, - cmd_salias_run + cmd_salias_run, + cmd_salias_complete, }; diff --git a/urjtag/src/cmd/cmd_set.c b/urjtag/src/cmd/cmd_set.c index 005b2028..c23f9541 100644 --- a/urjtag/src/cmd/cmd_set.c +++ b/urjtag/src/cmd/cmd_set.c @@ -117,9 +117,33 @@ cmd_set_help (void) "set"); } +static void +cmd_set_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + switch (token_point) + { + case 1: /* name */ + cmd_signal_complete (chain, matches, match_cnt, text, text_len); + break; + + case 2: /* direction */ + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "in"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "out"); + break; + + case 3: /* value */ + /* XXX: Only applies if token[1] == "out" ... */ + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "0"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "1"); + break; + } +} + const urj_cmd_t urj_cmd_set = { "set", N_("set external signal value"), cmd_set_help, - cmd_set_run + cmd_set_run, + cmd_set_complete, }; diff --git a/urjtag/src/cmd/cmd_shift.c b/urjtag/src/cmd/cmd_shift.c index 9741a3ab..b2d29792 100644 --- a/urjtag/src/cmd/cmd_shift.c +++ b/urjtag/src/cmd/cmd_shift.c @@ -77,9 +77,21 @@ cmd_shift_help (void) "shift ir", "shift dr"); } +static void +cmd_shift_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + if (token_point != 1) + return; + + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "dr"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "ir"); +} + const urj_cmd_t urj_cmd_shift = { "shift", N_("shift data/instruction registers through JTAG chain"), cmd_shift_help, - cmd_shift_run + cmd_shift_run, + cmd_shift_complete, }; diff --git a/urjtag/src/cmd/cmd_signal.c b/urjtag/src/cmd/cmd_signal.c index 23454850..c84c9ef1 100644 --- a/urjtag/src/cmd/cmd_signal.c +++ b/urjtag/src/cmd/cmd_signal.c @@ -101,6 +101,27 @@ cmd_signal_help (void) "signal"); } +/* This is used indirectly by other signal commands */ +void +cmd_signal_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len) +{ + urj_part_t *part; + urj_part_signal_t *s; + + part = urj_tap_chain_active_part (chain); + if (part == NULL) + return; + + s = part->signals; + while (s) + { + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, + s->name); + s = s->next; + } +} + const urj_cmd_t urj_cmd_signal = { "signal", N_("define new signal for a part"), diff --git a/urjtag/src/cmd/cmd_test.c b/urjtag/src/cmd/cmd_test.c index de8a700d..d88b2f03 100644 --- a/urjtag/src/cmd/cmd_test.c +++ b/urjtag/src/cmd/cmd_test.c @@ -108,9 +108,26 @@ cmd_test_help (void) "test"); } +static void +cmd_test_complete (urj_chain_t *chain, char ***matches, size_t *match_cnt, + const char *text, size_t text_len, size_t token_point) +{ + switch (token_point) + { + case 1: /* name */ + cmd_signal_complete (chain, matches, match_cnt, text, text_len); + break; + case 2: /* value */ + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "0"); + urj_completion_mayben_add_match (matches, match_cnt, text, text_len, "1"); + break; + } +} + const urj_cmd_t urj_cmd_test = { "test", N_("test external signal value"), cmd_test_help, - cmd_test_run + cmd_test_run, + cmd_test_complete, };