Commit 79bc2716 authored by Jiří Klimeš's avatar Jiří Klimeš

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
parent bf01da1a
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment