From 8695410d8692aa016f764803d5b7cfa4618ba5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Klime=C5=A1?= Date: Thu, 3 Oct 2013 09:13:23 +0100 Subject: [PATCH] cli: enhance TAB-completion in nmcli editor - completion now works for command shortcuts too nmcli> g ethe - completion now allows leading spaces nmcli> desc wifi.ss - fix completion of settings when property is already there nmcli> set con.id - fix completion of properties in setting.property nmcli> des con.inter bla --- cli/src/connections.c | 212 +++++++++++++++++++++++++++++------------- 1 file changed, 149 insertions(+), 63 deletions(-) diff --git a/cli/src/connections.c b/cli/src/connections.c index 543929826b..5bb12724ff 100644 --- a/cli/src/connections.c +++ b/cli/src/connections.c @@ -14,7 +14,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * (C) Copyright 2010 - 2012 Red Hat, Inc. + * (C) Copyright 2010 - 2013 Red Hat, Inc. */ #include "config.h" @@ -4498,7 +4498,7 @@ set_deftext (void) } static char * -gen_nmcli_cmds (char *text, int state, const char **commands) +gen_func_basic (char *text, int state, const char **words) { static int list_idx, len; const char *name; @@ -4508,8 +4508,8 @@ gen_nmcli_cmds (char *text, int state, const char **commands) len = strlen (text); } - /* Return the next name which partially matches from the command list. */ - while ((name = commands[list_idx])) { + /* Return the next name which partially matches one from the 'words' list. */ + while ((name = words[list_idx])) { list_idx++; if (strncmp (name, text, len) == 0) @@ -4521,40 +4521,54 @@ gen_nmcli_cmds (char *text, int state, const char **commands) static char * gen_nmcli_cmds_menu (char *text, int state) { - const char *commands[] = { "goto", "set", "remove", "describe", "print", "verify", - "save", "back", "help", "quit", "nmcli", - NULL }; - return gen_nmcli_cmds (text, state, commands); + const char *words[] = { "goto", "set", "remove", "describe", "print", "verify", + "save", "back", "help", "quit", "nmcli", + NULL }; + return gen_func_basic (text, state, words); } static char * gen_nmcli_cmds_submenu (char *text, int state) { - const char *commands[] = { "set", "add", "change", "remove", "describe", - "print", "back", "help", "quit", - NULL }; - return gen_nmcli_cmds (text, state, commands); + const char *words[] = { "set", "add", "change", "remove", "describe", + "print", "back", "help", "quit", + NULL }; + return gen_func_basic (text, state, words); } static char * gen_cmd_nmcli (char *text, int state) { - const char *commands[] = { "status-line", "save-confirmation", "prompt-color", NULL }; - return gen_nmcli_cmds (text, state, commands); + const char *words[] = { "status-line", "save-confirmation", "prompt-color", NULL }; + return gen_func_basic (text, state, words); +} + +static char * +gen_cmd_nmcli_prompt_color (char *text, int state) +{ + const char *words[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", NULL }; + return gen_func_basic (text, state, words); +} + +static char * +gen_func_bool_values (char *text, int state) +{ + const char *words[] = { "yes", "no", NULL }; + return gen_func_basic (text, state, words); } static char * gen_cmd_verify0 (char *text, int state) { - const char *commands[] = { "all", NULL }; - return gen_nmcli_cmds (text, state, commands); + const char *words[] = { "all", NULL }; + return gen_func_basic (text, state, words); } static char * gen_cmd_print2 (char *text, int state) { - const char *commands[] = { "setting", "connection", "all", NULL }; - return gen_nmcli_cmds (text, state, commands); + const char *words[] = { "setting", "connection", "all", NULL }; + return gen_func_basic (text, state, words); } static char * @@ -4618,22 +4632,17 @@ gen_property_names (char *text, int state) char *ret = NULL; char *line = g_strdup (*edit_lib_symbols.rl_line_buffer_x); const char *setting_name; - char *text_p = text; char **strv = NULL; const NameItem *valid_settings_arr; const char *p1; - /* Try to get the setting from 'line' - setting_name.partial_property */ + /* Try to get the setting from 'line' - setting_name.property */ p1 = strchr (line, '.'); if (p1) { while (p1 > line && !g_ascii_isspace (*p1)) p1--; strv = g_strsplit (p1+1, ".", 2); - if (g_strv_length (strv) == 2) - text_p = strv[1]; - else - text_p = ""; valid_settings_arr = get_valid_settings_array (nmc_completion_con_type); setting_name = check_valid_name (strv[0], valid_settings_arr, NULL); @@ -4645,7 +4654,7 @@ gen_property_names (char *text, int state) if (setting) { valid_props = nmc_setting_get_valid_properties (setting); - ret = gen_nmcli_cmds (text_p, state, (const char **) valid_props); + ret = gen_func_basic (text, state, (const char **) valid_props); } g_free (line); @@ -4656,33 +4665,98 @@ gen_property_names (char *text, int state) return ret; } +typedef char * (*my_gen_func_ptr) (char *, int); +static my_gen_func_ptr +get_gen_func_cmd_nmcli (char *str) +{ + if (!str) + return NULL; + if (matches (str, "status-line") == 0) + return gen_func_bool_values; + if (matches (str, "save-confirmation") == 0) + return gen_func_bool_values; + if (matches (str, "prompt-color") == 0) + return gen_cmd_nmcli_prompt_color; + return NULL; +} + /* - * Helper function to parse line for completion: - * TRUE - complete when "cmd ", "cmd stri" - * FALSE - don't complete when already "cmd string " + * Helper function parsing line for completion. + * IN: + * line : the whole line to be parsed + * end : the position of cursor in the line + * cmd : command to match + * OUT: + * cw_num : is set to the word number being completed (1, 2, 3, 4). + * prev_word : returns the previous word (so that we have some context). + * + * Returns TRUE when the first word of the 'line' matches 'cmd'. + * * Examples: - * "describe " - return TRUE - * "describe ipv" - return TRUE - * "describe 802-1x " - return FALSE - * "describe connection.i" - return TRUE - * "describe connection.id " - return FALSE + * line="rem" cmd="remove" -> TRUE cw_num=1 + * line="set con" cmd="set" -> TRUE cw_num=2 + * line="go ipv4.method" cmd="goto" -> TRUE cw_num=2 + * line=" des eth.mtu " cmd="describe" -> TRUE cw_num=3 + * line=" bla ipv4.method" cmd="goto" -> FALSE */ static gboolean -should_complete_cmd (const char *line, const char *cmd) +should_complete_cmd (const char *line, int end, const char *cmd, + int *cw_num, char **prev_word) { - if (g_str_has_prefix (line, cmd)) { - const char *p1 = line + strlen (cmd); - const char *p2; + char *tmp; + const char *word1, *word2, *word3; + size_t n1, n2, n3, n4, n5, n6; + gboolean word1_done, word2_done, word3_done; + gboolean ret = FALSE; - while (*p1 && g_ascii_isspace (*p1)) - p1++; - p2 = p1; - while (*p2 && !g_ascii_isspace (*p2)) - p2++; - if (*p2 == '\0') - return TRUE; + if (!line) + return FALSE; + + tmp = g_strdup (line); + + n1 = strspn (tmp, " \t"); + n2 = strcspn (tmp+n1, " \t\0") + n1; + n3 = strspn (tmp+n2, " \t") + n2; + n4 = strcspn (tmp+n3, " \t\0") + n3; + n5 = strspn (tmp+n4, " \t") + n4; + n6 = strcspn (tmp+n5, " \t\0") + n5; + + word1_done = end > n2; + word2_done = end > n4; + word3_done = end > n6; + tmp[n2] = tmp[n4] = tmp[n6] = '\0'; + + word1 = tmp[n1] ? tmp + n1 : NULL; + word2 = tmp[n3] ? tmp + n3 : NULL; + word3 = tmp[n5] ? tmp + n5 : NULL; + + if (!word1_done) { + if (cw_num) + *cw_num = 1; + if (prev_word) + *prev_word = NULL; + } else if (!word2_done) { + if (cw_num) + *cw_num = 2; + if (prev_word) + *prev_word = g_strdup (word1); + } else if (!word3_done) { + if (cw_num) + *cw_num = 3; + if (prev_word) + *prev_word = g_strdup (word2); + } else { + if (cw_num) + *cw_num = 4; + if (prev_word) + *prev_word = g_strdup (word3); } - return FALSE; + + if (word1 && matches (word1, cmd) == 0) + ret = TRUE; + + g_free (tmp); + return ret; } /* @@ -4701,6 +4775,9 @@ nmcli_editor_tab_completion (char *text, int start, int end) gboolean copy_char; const char *p1; char *p2, *prompt_tmp; + char *word = NULL; + size_t n1; + int num; /* Restore standard append character to space */ *edit_lib_symbols.rl_completion_append_character_x = ' '; @@ -4720,6 +4797,9 @@ nmcli_editor_tab_completion (char *text, int start, int end) } *p2 = '\0'; + /* Find the first non-space character */ + n1 = strspn (line, " \t"); + /* Choose the right generator function */ if (strcmp (prompt_tmp, EDITOR_PROMPT_CON_TYPE) == 0) generator_func = gen_connection_types; @@ -4730,41 +4810,46 @@ nmcli_editor_tab_completion (char *text, int start, int end) else if (g_str_has_prefix (prompt_tmp, "nmcli")) { if (!strchr (prompt_tmp, '.')) { int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1; + const char *dot = strchr (line, '.'); /* Main menu - level 0,1 */ - if (start == 0) + if (start == n1) generator_func = gen_nmcli_cmds_menu; else { - if (should_complete_cmd (line, "goto ")) { - if (level == 0 && !strchr (line, '.')) + if (should_complete_cmd (line, end, "goto", &num, NULL) && num <= 2) { + if (level == 0 && (!dot || dot >= line + end)) generator_func = gen_setting_names; else generator_func = gen_property_names; - } else if ( should_complete_cmd (line, "set ") - || should_complete_cmd (line, "remove ") - || should_complete_cmd (line, "describe ")) { - if (level == 0 && !strchr (line, '.')) { + } else if ( ( should_complete_cmd (line, end, "set", &num, NULL) + || should_complete_cmd (line, end, "remove", &num, NULL) + || should_complete_cmd (line, end, "describe", &num, NULL)) + && num <= 2) { + if (level == 0 && (!dot || dot >= line + end)) { generator_func = gen_setting_names; *edit_lib_symbols.rl_completion_append_character_x = '.'; - } - else + } else generator_func = gen_property_names; - } else if (should_complete_cmd (line, "nmcli ")) - generator_func = gen_cmd_nmcli; - else if ( should_complete_cmd (line, "print ") - || should_complete_cmd (line, "verify ")) - generator_func = gen_cmd_verify0; - else if (should_complete_cmd (line, "help ")) + } else if (should_complete_cmd (line, end, "nmcli", &num, &word)) { + if (num < 3) + generator_func = gen_cmd_nmcli; + else if (num == 3) + generator_func = get_gen_func_cmd_nmcli (word); + } else if ( should_complete_cmd (line, end, "print", &num, NULL) + || should_complete_cmd (line, end, "verify", &num, NULL)) { + if (num <= 2) + generator_func = gen_cmd_verify0; + } else if (should_complete_cmd (line, end, "help", &num, NULL) && num <= 2) generator_func = gen_nmcli_cmds_menu; } } else { /* Submenu - level 2 */ - if (start == 0) + if (start == n1) generator_func = gen_nmcli_cmds_submenu; else { - if (should_complete_cmd (line, "print ")) + if (should_complete_cmd (line, end, "print", &num, NULL) && num <= 2) generator_func = gen_cmd_print2; - else if (should_complete_cmd (line, "help ")) + else if (should_complete_cmd (line, end, "help", &num, NULL) && num <= 2) generator_func = gen_nmcli_cmds_submenu; } } @@ -4778,6 +4863,7 @@ nmcli_editor_tab_completion (char *text, int start, int end) *edit_lib_symbols.rl_attempted_completion_over_x = 1; g_free (prompt_tmp); + g_free (word); return match_array; }