diff --git a/include/asterisk/cli.h b/include/asterisk/cli.h index c79a4e93ce649eaf7c1921ba5c3fa87b188e79be..3ed88eb61da603aeaf90a5e8d1f7559a30f46af1 100644 --- a/include/asterisk/cli.h +++ b/include/asterisk/cli.h @@ -305,6 +305,27 @@ int ast_cli_generatornummatches(const char *, const char *); */ char **ast_cli_completion_matches(const char *, const char *); +/*! + * \brief Generates a vector of strings for CLI completion. + * + * \param text Complete input being matched. + * \param word Current word being matched + * + * The results contain strings that both: + * 1) Begin with the string in \a word. + * 2) Are valid in a command after the string in \a text. + * + * The first entry (offset 0) of the result is the longest common substring + * in the results, useful to extend the string that has been completed. + * Subsequent entries are all possible values. + * + * \note All strings and the vector itself are malloc'ed and must be freed + * by the caller. + * + * \note The vector is sorted and does not contain any duplicates. + */ +struct ast_vector_string *ast_cli_completion_vector(const char *text, const char *word); + /*! * \brief Command completion for the list of active channels. * diff --git a/main/cli.c b/main/cli.c index 5c16e8b7adfc65d8cab41458bd700b658f3137c9..b7626d4a73e220d83e4acd6df0f98b63fb2102f5 100644 --- a/main/cli.c +++ b/main/cli.c @@ -2488,76 +2488,98 @@ int ast_cli_generatornummatches(const char *text, const char *word) return matches; } -static void destroy_match_list(char **match_list, int matches) +char **ast_cli_completion_matches(const char *text, const char *word) { - if (match_list) { - int idx; + struct ast_vector_string *vec = ast_cli_completion_vector(text, word); + char **match_list; - for (idx = 1; idx < matches; ++idx) { - ast_free(match_list[idx]); - } - ast_free(match_list); + if (!vec) { + return NULL; + } + + if (AST_VECTOR_APPEND(vec, NULL)) { + /* We failed to NULL terminate the elements */ + AST_VECTOR_CALLBACK_VOID(vec, ast_free); + AST_VECTOR_PTR_FREE(vec); + + return NULL; } + + match_list = AST_VECTOR_STEAL_ELEMENTS(vec); + AST_VECTOR_PTR_FREE(vec); + + return match_list; } -char **ast_cli_completion_matches(const char *text, const char *word) +struct ast_vector_string *ast_cli_completion_vector(const char *text, const char *word) { - char **match_list = NULL, *retstr, *prevstr; - char **new_list; - size_t match_list_len, max_equal, which, i; - int matches = 0; + char *retstr, *prevstr; + size_t max_equal; + size_t which = 0; + struct ast_vector_string *vec = ast_calloc(1, sizeof(*vec)); - /* leave entry 0 free for the longest common substring */ - match_list_len = 1; - while ((retstr = ast_cli_generator(text, word, matches)) != NULL) { - if (matches + 1 >= match_list_len) { - match_list_len <<= 1; - new_list = ast_realloc(match_list, match_list_len * sizeof(*match_list)); - if (!new_list) { - destroy_match_list(match_list, matches); - return NULL; - } - match_list = new_list; + if (!vec) { + return NULL; + } + + while ((retstr = ast_cli_generator(text, word, which)) != NULL) { + if (AST_VECTOR_ADD_SORTED(vec, retstr, strcasecmp)) { + ast_free(retstr); + + goto vector_cleanup; } - match_list[++matches] = retstr; + + ++which; } - if (!match_list) { - return match_list; /* NULL */ + if (!AST_VECTOR_SIZE(vec)) { + AST_VECTOR_PTR_FREE(vec); + + return NULL; } + prevstr = AST_VECTOR_GET(vec, 0); + max_equal = strlen(prevstr); + which = 1; + /* Find the longest substring that is common to all results * (it is a candidate for completion), and store a copy in entry 0. */ - prevstr = match_list[1]; - max_equal = strlen(prevstr); - for (which = 2; which <= matches; which++) { - for (i = 0; i < max_equal && toupper(prevstr[i]) == toupper(match_list[which][i]); i++) + while (which < AST_VECTOR_SIZE(vec)) { + size_t i = 0; + + retstr = AST_VECTOR_GET(vec, which); + /* Check for and remove duplicate strings. */ + if (!strcasecmp(prevstr, retstr)) { + AST_VECTOR_REMOVE(vec, which, 1); + ast_free(retstr); + continue; + } + + while (i < max_equal && toupper(prevstr[i]) == toupper(retstr[i])) { + i++; + } + max_equal = i; + prevstr = retstr; + ++which; } - retstr = ast_malloc(max_equal + 1); - if (!retstr) { - destroy_match_list(match_list, matches); - return NULL; + /* Insert longest match to position 0. */ + retstr = ast_strndup(AST_VECTOR_GET(vec, 0), max_equal); + if (!retstr || AST_VECTOR_INSERT_AT(vec, 0, retstr)) { + ast_free(retstr); + goto vector_cleanup; } - ast_copy_string(retstr, match_list[1], max_equal + 1); - match_list[0] = retstr; - /* ensure that the array is NULL terminated */ - if (matches + 1 >= match_list_len) { - new_list = ast_realloc(match_list, (match_list_len + 1) * sizeof(*match_list)); - if (!new_list) { - ast_free(retstr); - destroy_match_list(match_list, matches); - return NULL; - } - match_list = new_list; - } - match_list[matches + 1] = NULL; + return vec; - return match_list; +vector_cleanup: + AST_VECTOR_CALLBACK_VOID(vec, ast_free); + AST_VECTOR_PTR_FREE(vec); + + return NULL; } /*! \brief returns true if there are more words to match */