cli: libreadline (libedit) TAB-completion for 'connection edit'
TODO: - test with libedit (it seems to be a bit different, buggy?)
This commit is contained in:
@@ -195,6 +195,10 @@ extern GMainLoop *loop;
|
||||
static ArgsInfo args_info;
|
||||
static guint progress_id = 0; /* ID of event source for displaying progress */
|
||||
|
||||
/* for readline TAB completion */
|
||||
static const char *nmc_completion_con_type = NULL;
|
||||
static NMSetting *nmc_completion_setting = NULL;
|
||||
|
||||
static void
|
||||
usage (void)
|
||||
{
|
||||
@@ -3272,15 +3276,27 @@ error:
|
||||
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
typedef char * (*ReadLineFunc) (const char *);
|
||||
typedef void (*AddHistoryFunc) (const char *);
|
||||
typedef int (*RlInsertTextFunc) (char *);
|
||||
typedef char *CPFunction ();
|
||||
typedef char **CPPFunction ();
|
||||
|
||||
typedef char * (*ReadLineFunc) (const char *);
|
||||
typedef void (*AddHistoryFunc) (const char *);
|
||||
typedef int (*RlInsertTextFunc) (char *);
|
||||
typedef char ** (*RlCompletionMatchesFunc) (char *, CPFunction *);
|
||||
|
||||
typedef struct {
|
||||
ReadLineFunc readline_func;
|
||||
AddHistoryFunc add_history_func;
|
||||
RlInsertTextFunc rl_insert_text_func;
|
||||
void **rl_startup_hook_x;
|
||||
RlCompletionMatchesFunc completion_matches_func;
|
||||
void **rl_attempted_completion_function_x;
|
||||
void **rl_completion_entry_function_x;
|
||||
char **rl_line_buffer_x;
|
||||
char **rl_prompt_x;
|
||||
int *rl_attempted_completion_over_x;
|
||||
int *rl_completion_append_character_x;
|
||||
const char **rl_completer_word_break_characters_x;
|
||||
} EditLibSymbols;
|
||||
|
||||
static EditLibSymbols edit_lib_symbols;
|
||||
@@ -3299,6 +3315,285 @@ set_deftext (void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_nmcli_cmds (char *text, int state, const char **commands)
|
||||
{
|
||||
static int list_idx, len;
|
||||
const char *name;
|
||||
|
||||
if (!state) {
|
||||
list_idx = 0;
|
||||
len = strlen (text);
|
||||
}
|
||||
|
||||
/* Return the next name which partially matches from the command list. */
|
||||
while ((name = commands[list_idx])) {
|
||||
list_idx++;
|
||||
|
||||
if (strncmp (name, text, len) == 0)
|
||||
return g_strdup (name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_nmcli_cmds_menu (char *text, int state)
|
||||
{
|
||||
const char *commands[] = { "goto", "set", "describe", "print", "verify",
|
||||
"save", "back", "help", "quit", "nmcli",
|
||||
NULL };
|
||||
return gen_nmcli_cmds (text, state, commands);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_cmd_nmcli (char *text, int state)
|
||||
{
|
||||
const char *commands[] = { "status-line", "prompt-color", NULL };
|
||||
return gen_nmcli_cmds (text, state, commands);
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_cmd_verify0 (char *text, int state)
|
||||
{
|
||||
const char *commands[] = { "all", NULL };
|
||||
return gen_nmcli_cmds (text, state, commands);
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_cmd_print2 (char *text, int state)
|
||||
{
|
||||
const char *commands[] = { "setting", "connection", "all", NULL };
|
||||
return gen_nmcli_cmds (text, state, commands);
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_connection_types (char *text, int state)
|
||||
{
|
||||
static int list_idx, len;
|
||||
const char *c_type;
|
||||
|
||||
if (!state) {
|
||||
list_idx = 0;
|
||||
len = strlen (text);
|
||||
}
|
||||
|
||||
while ( (c_type = nmc_valid_connection_types[list_idx].alias)
|
||||
|| (c_type = nmc_valid_connection_types[list_idx].name)) {
|
||||
list_idx++;
|
||||
if (!strncmp (text, c_type, len))
|
||||
return g_strdup (c_type);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_setting_names (char *text, int state)
|
||||
{
|
||||
static int list_idx, len;
|
||||
const char *s_name;
|
||||
const NameItem *valid_settings_arr;
|
||||
|
||||
if (!state) {
|
||||
list_idx = 0;
|
||||
len = strlen (text);
|
||||
}
|
||||
|
||||
valid_settings_arr = get_valid_settings_array (nmc_completion_con_type);
|
||||
if (!valid_settings_arr)
|
||||
return NULL;
|
||||
while ( (s_name = valid_settings_arr[list_idx].alias)
|
||||
|| (s_name = valid_settings_arr[list_idx].name)) {
|
||||
list_idx++;
|
||||
if (!strncmp (text, s_name, len))
|
||||
return g_strdup (s_name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *
|
||||
gen_property_names (char *text, int state)
|
||||
{
|
||||
NmcSettingNewFunc new_func;
|
||||
NMSetting *setting = NULL;
|
||||
char **valid_props = NULL;
|
||||
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 */
|
||||
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);
|
||||
new_func = nmc_setting_new_func (setting_name);
|
||||
if (!new_func)
|
||||
goto finish;
|
||||
setting = new_func ();
|
||||
}
|
||||
/* Else take the current setting, if any */
|
||||
if (!setting)
|
||||
setting = nmc_completion_setting ? g_object_ref (nmc_completion_setting) : NULL;
|
||||
if (!setting)
|
||||
goto finish;
|
||||
|
||||
valid_props = nmc_setting_get_valid_properties (setting);
|
||||
ret = gen_nmcli_cmds (text_p, state, (const char **) valid_props);
|
||||
|
||||
finish:
|
||||
g_free (line);
|
||||
g_strfreev (strv);
|
||||
g_strfreev (valid_props);
|
||||
if (setting)
|
||||
g_object_unref (setting);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to parse line for completion:
|
||||
* TRUE - complete when "cmd ", "cmd stri"
|
||||
* FALSE - don't complete when already "cmd string "
|
||||
* Examples:
|
||||
* "describe " - return TRUE
|
||||
* "describe ipv" - return TRUE
|
||||
* "describe 802-1x " - return FALSE
|
||||
* "describe connection.i" - return TRUE
|
||||
* "describe connection.id " - return FALSE
|
||||
*/
|
||||
static gboolean
|
||||
should_complete_cmd (const char *line, const char *cmd)
|
||||
{
|
||||
if (g_str_has_prefix (line, cmd)) {
|
||||
const char *p1 = line + strlen (cmd);
|
||||
const char *p2;
|
||||
|
||||
while (*p1 && g_ascii_isspace (*p1))
|
||||
p1++;
|
||||
p2 = p1;
|
||||
while (*p2 && !g_ascii_isspace (*p2))
|
||||
p2++;
|
||||
if (*p2 == '\0')
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to complete on the contents of TEXT. START and END show the
|
||||
* region of TEXT that contains the word to complete. We can use the
|
||||
* entire line in case we want to do some simple parsing. Return the
|
||||
* array of matches, or NULL if there aren't any.
|
||||
*/
|
||||
static char **
|
||||
nmcli_editor_tab_completion (char *text, int start, int end)
|
||||
{
|
||||
char **match_array = NULL;
|
||||
const char *line = *edit_lib_symbols.rl_line_buffer_x;
|
||||
const char *prompt = *edit_lib_symbols.rl_prompt_x;
|
||||
CPFunction *generator_func = NULL;
|
||||
gboolean copy_char;
|
||||
const char *p1;
|
||||
char *p2, *prompt_tmp;
|
||||
|
||||
/* Restore standard append character to space */
|
||||
*edit_lib_symbols.rl_completion_append_character_x = ' ';
|
||||
|
||||
/* Filter out possible ANSI color escape sequences */
|
||||
p1 = prompt;
|
||||
p2 = prompt_tmp = g_strdup (prompt);
|
||||
copy_char = TRUE;
|
||||
while (*p1) {
|
||||
if (*p1 == '\33')
|
||||
copy_char = FALSE;
|
||||
if (copy_char)
|
||||
*p2++ = *p1;
|
||||
if (!copy_char && *p1 == 'm')
|
||||
copy_char = TRUE;
|
||||
p1++;
|
||||
}
|
||||
*p2 = '\0';
|
||||
|
||||
/* Choose the right generator function */
|
||||
if (strcmp (prompt_tmp, EDITOR_PROMPT_CON_TYPE) == 0)
|
||||
generator_func = gen_connection_types;
|
||||
else if (strcmp (prompt_tmp, EDITOR_PROMPT_SETTING) == 0)
|
||||
generator_func = gen_setting_names;
|
||||
else if (strcmp (prompt_tmp, EDITOR_PROMPT_PROPERTY) == 0)
|
||||
generator_func = gen_property_names;
|
||||
else if (g_str_has_prefix (prompt_tmp, "nmcli")) {
|
||||
if (!strchr (prompt_tmp, '.')) {
|
||||
int level = g_str_has_prefix (prompt_tmp, "nmcli>") ? 0 : 1;
|
||||
|
||||
/* Main menu - level 0,1 */
|
||||
if (start == 0)
|
||||
generator_func = gen_nmcli_cmds_menu;
|
||||
else {
|
||||
if (should_complete_cmd (line, "goto ")) {
|
||||
if (level == 0 && !strchr (line, '.'))
|
||||
generator_func = gen_setting_names;
|
||||
else
|
||||
generator_func = gen_property_names;
|
||||
} else if ( should_complete_cmd (line, "set ")
|
||||
|| should_complete_cmd (line, "describe ")) {
|
||||
if (level == 0 && !strchr (line, '.')) {
|
||||
generator_func = gen_setting_names;
|
||||
*edit_lib_symbols.rl_completion_append_character_x = '.';
|
||||
}
|
||||
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 "))
|
||||
generator_func = gen_nmcli_cmds_menu;
|
||||
}
|
||||
} else {
|
||||
/* Submenu - level 2 */
|
||||
if (start == 0)
|
||||
generator_func = gen_nmcli_cmds_submenu;
|
||||
else {
|
||||
if (should_complete_cmd (line, "print "))
|
||||
generator_func = gen_cmd_print2;
|
||||
else if (should_complete_cmd (line, "help "))
|
||||
generator_func = gen_nmcli_cmds_submenu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (generator_func)
|
||||
match_array = edit_lib_symbols.completion_matches_func (text, generator_func);
|
||||
|
||||
/* Disable default filename completion */
|
||||
if (!match_array)
|
||||
*edit_lib_symbols.rl_attempted_completion_over_x = 1;
|
||||
|
||||
g_free (prompt_tmp);
|
||||
return match_array;
|
||||
}
|
||||
|
||||
static GModule *
|
||||
load_cmd_line_edit_lib (void)
|
||||
{
|
||||
@@ -3330,6 +3625,33 @@ load_cmd_line_edit_lib (void)
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_startup_hook", (gpointer) (&edit_lib_symbols.rl_startup_hook_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_attempted_completion_function",
|
||||
(gpointer) (&edit_lib_symbols.rl_attempted_completion_function_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "completion_matches",
|
||||
(gpointer) (&edit_lib_symbols.completion_matches_func)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_line_buffer",
|
||||
(gpointer) (&edit_lib_symbols.rl_line_buffer_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_prompt",
|
||||
(gpointer) (&edit_lib_symbols.rl_prompt_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_attempted_completion_over",
|
||||
(gpointer) (&edit_lib_symbols.rl_attempted_completion_over_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_completion_append_character",
|
||||
(gpointer) (&edit_lib_symbols.rl_completion_append_character_x)))
|
||||
goto error;
|
||||
if (!g_module_symbol (module, "rl_completer_word_break_characters",
|
||||
(gpointer) (&edit_lib_symbols.rl_completer_word_break_characters_x)))
|
||||
goto error;
|
||||
|
||||
/* Set a pointer to an alternative function to create matches */
|
||||
*edit_lib_symbols.rl_attempted_completion_function_x = (CPPFunction *) nmcli_editor_tab_completion;
|
||||
|
||||
/* Use ' ' and '.' as word break characters */
|
||||
*edit_lib_symbols.rl_completer_word_break_characters_x = ". ";
|
||||
|
||||
return module;
|
||||
error:
|
||||
@@ -4256,6 +4578,8 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
|
||||
nmc_setting_custom_init (setting);
|
||||
nm_connection_add_setting (connection, setting);
|
||||
}
|
||||
/* Set global variable for use in TAB completion */
|
||||
nmc_completion_setting = setting;
|
||||
|
||||
/* Switch to level 1 */
|
||||
menu_switch_to_level1 (&menu_ctx, setting, setting_name, nmc->editor_prompt_color);
|
||||
@@ -4446,6 +4770,7 @@ editor_menu_main (NmCli *nmc, NMConnection *connection, const char *connection_t
|
||||
/* Go back (up) an the menu */
|
||||
if (menu_ctx.level == 1) {
|
||||
menu_switch_to_level0 (&menu_ctx, BASE_PROMPT, nmc->editor_prompt_color);
|
||||
nmc_completion_setting = NULL; /* for TAB completion */
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -4772,6 +5097,9 @@ do_connection_edit (NmCli *nmc, int argc, char **argv)
|
||||
printf (_("Type 'help' or '?' for available commands."));
|
||||
printf ("\n\n");
|
||||
|
||||
/* Set global variable for use in TAB completion */
|
||||
nmc_completion_con_type = connection_type;
|
||||
|
||||
/* Run menu loop */
|
||||
editor_menu_main (nmc, connection, connection_type);
|
||||
|
||||
|
Reference in New Issue
Block a user