diff options
author | Jiří Klimeš <jklimes@redhat.com> | 2015-04-22 10:47:06 +0200 |
---|---|---|
committer | Jiří Klimeš <jklimes@redhat.com> | 2015-05-28 10:13:52 +0200 |
commit | 79bc2716852a3156732fa5e80fbf0aef7a336d5c (patch) | |
tree | 7624b09b0ca832cbe79cece49acad0eb801eb65b | |
parent | bf01da1a0815f83b0038055bfdf469fbd9f86755 (diff) | |
download | NetworkManager-79bc2716852a3156732fa5e80fbf0aef7a336d5c.tar.gz |
cli: TAB-completion for enum-style property values (rh #1034126)
Valid values for enumeration-style properties are offered in TAB-completion in
the editor. Thus the user has a quick overview of the possible values and can
edit properties more easily.
Example:
$ nmcli con edit type wifi
nmcli> set wifi-sec.group <TAB>
ccmp tkip wep104 wep40
nmcli> ...
https://bugzilla.redhat.com/show_bug.cgi?id=1034126
-rw-r--r-- | clients/cli/connections.c | 215 |
1 files changed, 170 insertions, 45 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 9a6e5912c0..89225f34f8 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -234,6 +234,7 @@ typedef struct { char *con_type; NMConnection *connection; NMSetting *setting; + const char *property; } TabCompletionInfo; static TabCompletionInfo nmc_tab_completion = {NULL, NULL, NULL, NULL}; @@ -5895,7 +5896,6 @@ uuid_display_hook (char **array, int len, int max_len) int i, max = 0; char *tmp; const char *id; - for (i = 1; i <= len; i++) { con = nmc_find_connection (nmc_tab_completion.nmc->connections, "uuid", array[i], NULL); id = con ? nm_connection_get_id (con) : NULL; @@ -6250,48 +6250,105 @@ should_complete_cmd (const char *line, int end, const char *cmd, return ret; } -static char * -extract_property_name (const char *prompt, const char *line) +/* + * extract_setting_and_property: + * prompt: (in) (allow-none): prompt string, or NULL + * line: (in) (allow-none): line, or NULL + * setting: (out) (transfer full) (array zero-terminated=1): + * return location for setting name + * property: (out) (transfer full) (array zero-terminated=1): + * return location for property name + * + * Extract setting and property names from prompt and/or line. + */ +static void +extract_setting_and_property (const char *prompt, const char *line, + char **setting, char **property) { char *prop = NULL; + char *sett = NULL; - /* If prompt is set take the property name from it, else extract it from line */ - if (!prompt) { - const char *p1; - size_t num; - p1 = strchr (line, '.'); - if (p1) { - p1++; - } else { - size_t n1, n2, n3; - n1 = strspn (line, " \t"); - n2 = strcspn (line+n1, " \t\0") + n1; - n3 = strspn (line+n2, " \t") + n2; - p1 = line + n3; - } - num = strcspn (p1, " \t\0"); - prop = g_strndup (p1, num); - } else { - const char *p1, *dot; - size_t num; + if (prompt) { + /* prompt looks like this: + "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */ + const char *p1, *p2, *dot; + size_t num1, num2; p1 = strchr (prompt, ' '); - /* prompt looks like this: "nmcli 802-1x>" or "nmcli 802-1x.pac-file>" */ if (p1) { - dot = strchr (p1 + 1, '.'); - p1 = dot ? dot + 1 : p1; - num = strcspn (p1, ">"); - prop = g_strndup (p1, num); + dot = strchr (++p1, '.'); + if (dot) { + p2 = dot + 1; + num1 = strcspn (p1, "."); + num2 = strcspn (p2, ">"); + sett = num1 > 0 ? g_strndup (p1, num1) : NULL; + prop = num2 > 0 ? g_strndup (p2, num2) : NULL; + } else { + num1 = strcspn (p1, ">"); + sett = num1 > 0 ? g_strndup (p1, num1) : NULL; + } } } - return prop; + if (line) { + /* line looks like this: + " set 802-1x.pac-file ..." or " set pac-file ..." */ + const char *p1, *p2, *dot; + size_t n1, n2, n3, n4; + size_t num1, num2, len; + n1 = strspn (line, " \t"); /* white-space */ + n2 = strcspn (line+n1, " \t\0") + n1; /* command */ + n3 = strspn (line+n2, " \t") + n2; /* white-space */ + n4 = strcspn (line+n3, " \t\0") + n3; /* setting/property */ + p1 = line + n3; + len = n4 - n3; + + dot = strchr (p1, '.'); + if (dot && dot < p1 + len) { + p2 = dot + 1; + num1 = strcspn (p1, "."); + num2 = len > num1 + 1 ? len - num1 - 1 : 0; + sett = num1 > 0 ? g_strndup (p1, num1) : sett; + prop = num2 > 0 ? g_strndup (p2, num2) : prop; + } else { + if (!prop) + prop = len > 0 ? g_strndup (p1, len) : NULL; + } + } + + if (setting) + *setting = sett; + else + g_free (sett); + if (property) + *property = prop; + else + g_free (prop); } static gboolean -should_complete_files (const char *prompt, const char *line) +_get_and_check_property (const char *prompt, + const char *line, + const char **array, + const char **array_multi, + gboolean *multi) { char *prop; gboolean found = FALSE; + + extract_setting_and_property (prompt, line, NULL, &prop); + if (prop) { + if (array) + found = !!nmc_string_is_valid (prop, array, NULL); + if (array_multi && multi) + *multi = !!nmc_string_is_valid (prop, array_multi, NULL); + g_free (prop); + } + return found; +} + +static gboolean +should_complete_files (const char *prompt, const char *line) +{ const char *file_properties[] = { /* '802-1x' properties */ "ca-cert", @@ -6307,32 +6364,86 @@ should_complete_files (const char *prompt, const char *line) "config", NULL }; - - prop = extract_property_name (prompt, line); - if (prop) { - found = !!nmc_string_is_valid (prop, file_properties, NULL); - g_free (prop); - } - return found; + return _get_and_check_property (prompt, line, file_properties, NULL, NULL); } static gboolean should_complete_vpn_uuids (const char *prompt, const char *line) { - char *prop; - gboolean found = FALSE; const char *uuid_properties[] = { /* 'connection' properties */ "secondaries", NULL }; + return _get_and_check_property (prompt, line, uuid_properties, NULL, NULL); +} - prop = extract_property_name (prompt, line); - if (prop) { - found = !!nmc_string_is_valid (prop, uuid_properties, NULL); - g_free (prop); - } - return found; +static char *is_property_valid (NMSetting *setting, const char *property, GError **error); +static const char ** +get_allowed_property_values (void) +{ + const NameItem *valid_settings_arr; + const char *setting_name; + NMSetting *setting = NULL; + char *property = NULL; + char *sett = NULL, *prop = NULL; + const char **avals = NULL; + + extract_setting_and_property (rl_prompt, rl_line_buffer, &sett, &prop); + if (sett) { + valid_settings_arr = get_valid_settings_array (nmc_tab_completion.con_type); + setting_name = check_valid_name (sett, valid_settings_arr, NULL); + setting = nmc_setting_new_for_name (setting_name); + } else + setting = nmc_tab_completion.setting ? g_object_ref (nmc_tab_completion.setting) : NULL; + + if (setting && prop) + property = is_property_valid (setting, prop, NULL); + else + property = g_strdup (nmc_tab_completion.property); + + if (setting && property) + avals = nmc_setting_get_property_allowed_values (setting, property); + + g_free (sett); + g_free (prop); + if (setting) + g_object_unref (setting); + g_free (property); + return avals; +} + +static gboolean +should_complete_property_values (const char *prompt, const char *line, gboolean *multi) +{ + /* properties allowing multiple values */ + const char *multi_props[] = { + /* '802-1x' properties */ + NM_SETTING_802_1X_EAP, + /* '802-11-wireless-security' properties */ + NM_SETTING_WIRELESS_SECURITY_PROTO, + NM_SETTING_WIRELESS_SECURITY_PAIRWISE, + NM_SETTING_WIRELESS_SECURITY_GROUP, + /* 'bond' properties */ + NM_SETTING_BOND_OPTIONS, + /* 'ethernet' properties */ + NM_SETTING_WIRED_S390_OPTIONS, + NULL + }; + _get_and_check_property (prompt, line, NULL, multi_props, multi); + return get_allowed_property_values () != NULL; +} + +static char * +gen_property_values (const char *text, int state) +{ + char *ret = NULL; + const char **avals; + + avals = get_allowed_property_values (); + if (avals) + ret = nmc_rl_gen_func_basic (text, state, avals); + return ret; } /* from readline */ @@ -6387,6 +6498,7 @@ nmcli_editor_tab_completion (const char *text, int start, int end) if (!strchr (prompt_tmp, '.')) { int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1; const char *dot = strchr (line, '.'); + gboolean multi; /* Main menu - level 0,1 */ if (start == n1) @@ -6407,9 +6519,12 @@ nmcli_editor_tab_completion (const char *text, int start, int end) } else if (num >= 3) { if (num == 3 && should_complete_files (NULL, line)) rl_attempted_completion_over = 0; - if (should_complete_vpn_uuids (NULL, line)) { + else if (should_complete_vpn_uuids (NULL, line)) { rl_completion_display_matches_hook = uuid_display_hook; generator_func = gen_vpn_uuids; + } else if ( should_complete_property_values (NULL, line, &multi) + && (num == 3 || multi)) { + generator_func = gen_property_values; } } } else if ( ( should_complete_cmd (line, end, "remove", &num, NULL) @@ -6444,6 +6559,8 @@ nmcli_editor_tab_completion (const char *text, int start, int end) if (start == n1) generator_func = gen_nmcli_cmds_submenu; else { + gboolean multi; + if ( should_complete_cmd (line, end, "add", &num, NULL) || should_complete_cmd (line, end, "set", &num, NULL)) { if (num <= 2 && should_complete_files (prompt_tmp, line)) @@ -6451,6 +6568,9 @@ nmcli_editor_tab_completion (const char *text, int start, int end) else if (should_complete_vpn_uuids (prompt_tmp, line)) { rl_completion_display_matches_hook = uuid_display_hook; generator_func = gen_vpn_uuids; + } else if ( should_complete_property_values (prompt_tmp, NULL, &multi) + && (num <= 2 || multi)) { + generator_func = gen_property_values; } } if (should_complete_cmd (line, end, "print", &num, NULL) && num <= 2) @@ -7164,6 +7284,9 @@ property_edit_submenu (NmCli *nmc, gboolean temp_changes; gboolean removed; + /* Set global variable for use in TAB completion */ + nmc_tab_completion.property = prop_name; + prompt = nmc_colorize (nmc->editor_prompt_color, NMC_TERM_FORMAT_NORMAL, "nmcli %s.%s> ", nm_setting_get_name (curr_setting), prop_name); @@ -7300,6 +7423,8 @@ property_edit_submenu (NmCli *nmc, break; case NMC_EDITOR_SUB_CMD_BACK: + /* Set global variable for use in TAB completion */ + nmc_tab_completion.property = NULL; cmd_property_loop = FALSE; break; |