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
This commit is contained in:
@@ -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
|
||||
_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)
|
||||
{
|
||||
char *prop;
|
||||
gboolean found = FALSE;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
return found;
|
||||
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;
|
||||
|
||||
|
Reference in New Issue
Block a user