From 17d8362e5c62972ad85a310555c8a16e54091ed3 Mon Sep 17 00:00:00 2001 From: LokLiang Date: Tue, 25 Mar 2025 17:47:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=20shell=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=BB=93=E6=9E=84=E4=BD=93=EF=BC=8C=E6=9B=B4=E6=96=B0=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=90=8D=E7=A7=B0=E4=BB=A5=E6=8F=90=E9=AB=98=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/system/source/shell/sh.c | 1582 +++++++++++++-------- components/system/source/shell/sh.h | 60 +- components/system/source/shell/sh_vt100.c | 6 +- 3 files changed, 1044 insertions(+), 604 deletions(-) diff --git a/components/system/source/shell/sh.c b/components/system/source/shell/sh.c index 23e3921..230ead6 100755 --- a/components/system/source/shell/sh.c +++ b/components/system/source/shell/sh.c @@ -20,9 +20,9 @@ #define SYS_LOG_DOMAIN "sh" #include "sys_log.h" -#ifndef CONFIG_SH_MAX_PARAM -#define CONFIG_SH_MAX_PARAM (CONFIG_SH_MAX_LINE_LEN / 5) /* 以空格为分隔符的最多命令段数 */ -#endif +#define _MAX_DETAIL_LINES 30 /* 自动补全超过最大候选数时以列表形式显示 */ + +#define _MAX_SAVE_DEPTH (1 + CONFIG_SH_MAX_PARAM) #define _SH_ARRAY_COUNT(NAME) (sizeof(NAME) / sizeof(NAME[0])) #define _IS_ALPHA(c) ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) @@ -45,42 +45,79 @@ #define _DAY ((__DATE__[4] == ' ' ? 0 : __DATE__[4] - '0') * 10 + (__DATE__[5] - '0')) +typedef struct +{ + int argc; + const char **argv; + uint16_t save_depth[_MAX_SAVE_DEPTH]; +} __cp_param_t; + +typedef enum +{ + _CP_UPDATE_TYPE_UPDATE, // 仅更新补全信息 + _CP_UPDATE_TYPE_TEST, // 去重复后的 cp_info->match_num + _CP_UPDATE_TYPE_PRINT, // 打印补全信息 +} __cp_update_type_t; + +typedef void (*__cp_update_cb)( + sh_t *sh_hdl, + sh_cp_info_t *cp_info, + const sh_cmd_t *sub_cmd, + char *prefix, + int argc, + const char **argv); + static sys_pslist_t s_cmd_list; // 已注册的根命令链 static sys_pslist_t s_cmd_list_hide; // 已注册的根命令链(不会出现在补全中) -static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code); // 在热键链中查找完全匹配的节点 -static const sh_cmd_t *_find_cmd(sh_t *sh_hdl, const sh_cmd_reg_t **dest_reg_struct, const sh_cmd_t *sub_cmd, const char *cmd); // 在所有命令结构中中查找完全匹配的节点 -static const sh_cmd_t *_find_root_cmd(sh_t *sh_hdl, const sh_cmd_reg_t **dest_reg_struct, const char *cmd); // 在根命令链中查找完全匹配的节点 -static int _register_cmd(sh_t *sh_hdl, sys_pslist_t *list, const sh_cmd_reg_t *sh_reg); // 注册命令 -static int _register_cmd_check_fn(char *err_cmd, const sh_cmd_t *cmd); // 检查 sh_cmd_t::fn 的类型是否符合规则 -static void _find_and_run_key(sh_t *sh_hdl, char c); // 一系列动作:保存一个字符到热键缓存,并尝试查找对应的热键功能函数。缓存会被自动清除。 -static void _insert_str(sh_t *sh_hdl, const char *str); // 在当前的光标前插入一个字符并显示 -static void _read_param(int *argc, const char *argv[CONFIG_SH_MAX_PARAM], char *dest, const char *src); // 从 src 中取得结果,输出到 int *argc, char **argv[], char *dest -static void _store_history(sh_t *sh_hdl, const char *src); // 保存历史记录 -static void _apply_history(sh_t *sh_hdl); // 当在回翻历史记录时,一旦对记录修改则立即将当前记录复制到缓存中 -static unsigned _get_end_pos(sh_t *sh_hdl); // 根据当前显示内容,获取最大光标位置 +static sh_cmd_info_t _find_registered_command(sh_t *sh_hdl, + sys_pslist_t *new_list, + uint16_t save_depth[_MAX_SAVE_DEPTH], + int argc, const char *argv[]); +static const sh_cmd_t *_match_command_recursive(uint16_t *save_depth, unsigned *save_cmd_count, const sh_cmd_t *cmd, int argc, const char *argv[]); + +static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code); // 在热键链中查找完全匹配的节点 +static int _register_cmd(sh_t *sh_hdl, sys_pslist_t *list, const sh_cmd_reg_t *sh_reg); // 注册命令 +static int _register_cmd_check_fn(sh_t *sh_hdl, const sh_cmd_reg_t *sh_reg, char *err_cmd, const sh_cmd_t *cmd); // 检查 sh_cmd_t::fn 的类型是否符合规则 +static void _find_and_run_key(sh_t *sh_hdl, char c); // 一系列动作:保存一个字符到热键缓存,并尝试查找对应的热键功能函数。缓存会被自动清除。 +static void _insert_str(sh_t *sh_hdl, const char *str); // 在当前的光标前插入一个字符并显示 +static char *_parse_input(int *argc, const char *argv[], char *dest, const char *src); // 从 src 中取得结果,输出到 int *argc, char **argv[], char *dest +static void _store_history(sh_t *sh_hdl, const char *src); // 保存历史记录 +static void _apply_history(sh_t *sh_hdl); // 当在回翻历史记录时,一旦对记录修改则立即将当前记录复制到缓存中 +static unsigned _get_end_pos(sh_t *sh_hdl); // 根据当前显示内容,获取最大光标位置 static void _store_cmd_bak(sh_t *sh_hdl, const char *line, int pos, int n); // 保存擦除的数据 -static void _clear_argv(int argc, const char *argv[]); // 使剩余的字符串指针指向空字符串 static const char *sh_ctrl_get_prompt(sh_t *sh_hdl); // 获取当前提示符 static const char *sh_ctrl_get_line(sh_t *sh_hdl); // 获取当前已输入(或回显的历史)的字符串 static sh_cp_info_t _completion_init(const char *cmd); // 初始化待自动补全数据 +static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], int flag_parent, __cp_update_cb update_cb, char *prefix); +static bool _do_completion_cmd(sh_t *sh_hdl, + sh_cp_info_t *cp_info, + __cp_param_t *cp_param, + bool print_match, + int flag_parent, + __cp_update_cb update_cb, + char *prefix); static void _completion_update( sh_t *sh_hdl, - sh_cp_info_t *info, + sh_cp_info_t *cp_info, const sh_cmd_t *sub_cmd, - bool print_match, + bool suffix_space, + __cp_update_type_t update_type, + const char *unique_commands_buf[], int max_lines, - int flag_parent); -static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], int flag_parent); -static bool _do_completion_cmd(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cmd_info_t *cmd_info, bool print_match, int flag_parent); // 执行一次自动补全命令的功能 -static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cp_param_t *sh_param, bool print_match); // 执行一次自动补全参数的功能 -static bool _do_completion_insert(sh_t *sh_hdl, sh_cp_info_t *info); // 执行自动补全 -static void _do_restore_line(sh_t *sh_hdl); // 在展示选项后恢复命令行的显示 -static int _calc_list_algin(sh_cp_info_t *info); // 确定每个命令的对齐的长度 -static bool _arg_is_end(sh_t *sh_hdl); // 当前输入的最后一个参数是否完整(以空格分开) + int flag_parent, + __cp_update_cb update_cb, + char *prefix, + int argc, + const char **argv); +static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *cp_info, const sh_cp_param_t *sh_param, bool print_match); // 执行一次自动补全参数的功能 +static bool _do_completion_insert(sh_t *sh_hdl, sh_cp_info_t *cp_info); // 执行自动补全 +static void _do_restore_line(sh_t *sh_hdl); // 在展示选项后恢复命令行的显示 +static int _calc_list_algin(sh_cp_info_t *cp_info); // 确定每个命令的对齐的长度 +static bool _arg_is_end(sh_t *sh_hdl); // 当前输入的最后一个参数是否完整(以空格分开) static void _update_cursor(sh_t *sh_hdl); // 同步热键功能:更新光标位置记录 @@ -89,6 +126,117 @@ static const char *_skipwhite(const char *q); static double _strtod(const char *str, char **end); // https://gitee.com/mirrors_mattn/strtod/blob/master/strtod.c #endif +static sh_cmd_info_t _find_registered_command(sh_t *sh_hdl, + sys_pslist_t *new_list, + uint16_t save_depth[_MAX_SAVE_DEPTH], + int argc, const char *argv[]) +{ + sys_pslist_t *cmd_lists[] = { + NULL, + &s_cmd_list, + &s_cmd_list_hide, + new_list, + }; + sh_cmd_info_t ret; + + if (sh_hdl) + { + cmd_lists[0] = &sh_hdl->cmd_list; + } + + ret.match_status = _SH_CMD_STATUS_NotFound; + ret.cmd_count = 0; + ret.reg_struct = NULL; + ret.match_cmd = NULL; + + if (argc == 0) + { + ret.match_status = _SH_CMD_STATUS_NeedsSubcommand; + return ret; + } + + int index = 0; + for (int i = 0; i < _SH_ARRAY_COUNT(cmd_lists); i++) + { + sys_pslist_t *test_list = cmd_lists[i]; + if (test_list != NULL) + { + const sh_cmd_reg_t *sh_reg; + SYS_PSLIST_FOR_EACH_CONTAINER(test_list, sh_reg, reg_node) + { + if (index >= save_depth[0]) + { + ret.cmd_count = 0; + ret.match_cmd = _match_command_recursive(&save_depth[1], &ret.cmd_count, sh_reg->cmd, argc, argv); + + if (ret.match_cmd != NULL) + { + save_depth[0] = index; + + if (ret.match_cmd->cmd_func) // 找到最终命令 + { + ret.match_status = _SH_CMD_STATUS_Success; + ret.reg_struct = sh_reg; + return ret; + } + + if (ret.cmd_count == argc && ret.match_cmd->fn_type == _SH_SUB_TYPE_SUB) // 有子命令 + { + ret.match_status = _SH_CMD_STATUS_NeedsSubcommand; + ret.reg_struct = sh_reg; + return ret; + } + } + } + + index++; + } + } + } + + return ret; +} + +static const sh_cmd_t *_match_command_recursive(uint16_t *save_depth, unsigned *save_cmd_count, const sh_cmd_t *cmd, int argc, const char *argv[]) +{ + if (*save_cmd_count >= argc) + { + *save_depth = 0; + return NULL; + } + + for (int i = *save_depth; *(int *)&cmd[i] != 0; i++) // 从上次保存的位置开始,遍历有效的命令 + { + if ((cmd[i].cmd_func != NULL || cmd[i].sub_fn.sub_cmd != NULL) && // 有效的父命令或最终命令函数 + strcmp(cmd[i].cmd, argv[*save_cmd_count]) == 0) // session 段成功匹配 + { + *save_cmd_count += 1; + if (*save_cmd_count == argc || // 完全匹配 + cmd[i].cmd_func != NULL) // 最终命令 + { + *save_depth = i + 1; + return &cmd[i]; + } + else // 未完全匹配 + { + if (cmd[i].cmd_func == NULL && cmd[i].fn_type == _SH_SUB_TYPE_SUB) // 有子命令 + { + const sh_cmd_t *ret = _match_command_recursive(&save_depth[1], save_cmd_count, cmd[i].sub_fn.sub_cmd, argc, argv); // 递归查检子命令 + if (ret) + { + *save_depth = i; + return ret; + } + } + } + *save_cmd_count -= 1; + } + } + + *save_depth = 0; + return NULL; +} + /** * @brief 查找由 sh_register_key() 注册的完全匹配的节点 * @@ -98,8 +246,8 @@ static double _strtod(const char *str, char **end); // https://gitee.com/mirrors static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code) { const sh_key_reg_t *test_reg_key; - int match_count = 0; - SYS_PSLIST_FOR_EACH_CONTAINER(&sh_hdl->key_list, test_reg_key, node) + int cmd_count = 0; + SYS_PSLIST_FOR_EACH_CONTAINER(&sh_hdl->key_list, test_reg_key, reg_node) { const sh_key_t *test_key = test_reg_key->key; for (int i = 0; *(int *)&test_key[i] != 0; i++) @@ -110,12 +258,12 @@ static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code) } if (strncmp(test_key[i].code, code, sh_hdl->key_stored) == 0) { - match_count++; + cmd_count++; } } } - if (match_count == 0) + if (cmd_count == 0) { sh_hdl->key_stored = 0; sh_hdl->key_str[sh_hdl->key_stored] = '\0'; @@ -123,104 +271,6 @@ static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code) return NULL; } -/** - * @brief 查找由 sh_register_cmd() sh_register_cmd_hide() sh_register_key_cmd() 注册的完全匹配的单元命令节点。 - * 如果当前为模块模式,由优先在模块中查找。 - * - * @param dest_reg_struct[out] 保存根命令结构的定义地址 - * @param sub_cmd NULL -- 从根命令中遍历查找; ! NULL 从已知的父命令节点中查找 - * @param cmd 单元命令字符串 - * @return const sh_cmd_t* NULL -- 没有记录这个命令; ! NULL 找到的命令节点 - */ -static const sh_cmd_t *_find_cmd(sh_t *sh_hdl, const sh_cmd_reg_t **dest_reg_struct, const sh_cmd_t *sub_cmd, const char *cmd) -{ - const sh_cmd_t *test_cmd; - if (sh_hdl->select_cmd && sub_cmd == NULL) - { - test_cmd = sh_hdl->select_cmd->sub_fn.sub_cmd; - for (int i = 0; test_cmd[i].cmd != NULL; i++) - { - if ((test_cmd->cmd_func != NULL || test_cmd->sub_fn.sub_cmd != NULL) && - strcmp(test_cmd[i].cmd, cmd) == 0) - { - if (dest_reg_struct) - { - *dest_reg_struct = sh_hdl->select_reg_struct; - } - return &test_cmd[i]; - } - } - } - - if (sub_cmd == NULL) - { - if (dest_reg_struct) - { - *dest_reg_struct = NULL; - } - return _find_root_cmd(sh_hdl, dest_reg_struct, cmd); - } - else - { - test_cmd = sub_cmd; - for (int i = 0; test_cmd[i].cmd != NULL; i++) - { - if ((test_cmd->cmd_func != NULL || test_cmd->sub_fn.sub_cmd != NULL) && - strcmp(test_cmd[i].cmd, cmd) == 0) - { - return &test_cmd[i]; - } - } - return NULL; - } -} - -/** - * @brief 仅在根链表中查找由 sh_register_cmd() sh_register_cmd_hide() sh_register_key_cmd() 注册的完全匹配的单元命令节点。 - * - * @param dest_reg_struct[out] 保存根命令结构的定义地址 - * @param cmd 单元命令字符串 - * @return const sh_cmd_t* NULL -- 没有记录这个命令; ! NULL 找到的命令节点 - */ -static const sh_cmd_t *_find_root_cmd(sh_t *sh_hdl, const sh_cmd_reg_t **dest_reg_struct, const char *cmd) -{ - const sh_cmd_reg_t *sh_reg; - sys_pslist_t *cmd_list[] = { - &s_cmd_list_hide, - &s_cmd_list, - NULL, - }; - if (sh_hdl) - { - cmd_list[sizeof(cmd_list) / sizeof(cmd_list[0]) - 1] = &sh_hdl->cmd_list; - } - for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_list[0]); i++) - { - sys_pslist_t *test_list = cmd_list[i]; - if (test_list != NULL) - { - SYS_PSLIST_FOR_EACH_CONTAINER(test_list, sh_reg, node) - { - const sh_cmd_t *reg_cmd = sh_reg->cmd; - for (int i = 0; *(int *)®_cmd[i] != 0; i++) - { - const sh_cmd_t *test_cmd = ®_cmd[i]; - if ((test_cmd->cmd_func != NULL || test_cmd->sub_fn.sub_cmd != NULL) && - strcmp(test_cmd->cmd, cmd) == 0) - { - if (dest_reg_struct) - { - *dest_reg_struct = sh_reg; - } - return test_cmd; - } - } - } - } - } - return NULL; -} - /** * @brief 执行注册根命令 * @@ -230,39 +280,22 @@ static const sh_cmd_t *_find_root_cmd(sh_t *sh_hdl, const sh_cmd_reg_t **dest_re */ static int _register_cmd(sh_t *sh_hdl, sys_pslist_t *list, const sh_cmd_reg_t *sh_reg) { - const sh_cmd_t *reg_cmd = sh_reg->cmd; - for (int i = 0; *(int *)®_cmd[i] != 0; i++) + sys_pslist_find_and_remove(list, &sh_reg->reg_node); + + const sh_cmd_t *sh_cmd = sh_reg->cmd; + for (int i = 0; *(int *)&sh_cmd[i] != 0; i++) { - const sh_cmd_t *test_cmd = ®_cmd[i]; - char err_cmd[0x100]; + const sh_cmd_t *test_cmd = &sh_cmd[i]; + char err_cmd[_SH_ARRAY_COUNT(sh_hdl->cmd_line) + 1]; + err_cmd[0] = '\0'; - if (_register_cmd_check_fn(err_cmd, test_cmd)) + if (_register_cmd_check_fn(sh_hdl, sh_reg, err_cmd, test_cmd)) { - SYS_LOG_WRN("sub_fn: pointer type mismatch in conditional expression!\r\n" - "Defined in file: %s:%d\r\n" - "Command:%s", - sh_reg->file, - sh_reg->line, - err_cmd); - return -1; - } - - if (strchr(test_cmd->cmd, ' ')) - { - if (sh_hdl && sh_hdl->disable_echo == 0) - SYS_LOG_WRN("Command cannot contain spaces: '%s'\r\n", test_cmd->cmd); - return -1; - } - - if (_find_root_cmd(sh_hdl, NULL, test_cmd->cmd)) - { - if (sh_hdl && sh_hdl->disable_echo == 0) - SYS_LOG_WRN("Repeat command: '%s'\r\n", test_cmd->cmd); return -1; } } - sys_pslist_append(list, &sh_reg->node); + sys_pslist_append(list, &sh_reg->reg_node); return 0; } @@ -270,49 +303,127 @@ static int _register_cmd(sh_t *sh_hdl, sys_pslist_t *list, const sh_cmd_reg_t *s /** * @brief _register_cmd() 专用,递归遍历一个根命令及所有子命令,确认 sh_cmd_t::fn 的类型是否符合规则 * + * @param sh_reg 根命令数据 * @param err_cmd[out] 输出记录错误的命令路径 * @param cmd 命令 * @retval 0 所有命令的 sh_cmd_t::fn 类型符合规则 * @retval -1 有错误的 sh_cmd_t::fn 类型 */ -static int _register_cmd_check_fn(char *err_cmd, const sh_cmd_t *cmd) +static int _register_cmd_check_fn(sh_t *sh_hdl, const sh_cmd_reg_t *sh_reg, char *err_cmd, const sh_cmd_t *cmd) { for (int i = 0; *(int *)&cmd[i] != 0; i++) { + char *err_cmd_bak = &err_cmd[strlen(err_cmd)]; + + if (strlen(cmd->cmd) >= CONFIG_SH_MAX_CMD_LEN) + { + if (sh_hdl && sh_hdl->disable_echo == 0) + { + SYS_LOG_WRN("cmd: Command length exceeds the maximum limit!\r\n" + "Defined in file: %s:%d\r\n" + "Command: '%s'", + _FILENAME(sh_reg->file), + sh_reg->line, + cmd->cmd); + } + return -1; + } + + if (strchr(cmd->cmd, ' ')) + { + if (sh_hdl && sh_hdl->disable_echo == 0) + { + SYS_LOG_WRN("cmd: Command cannot contain spaces!\r\n" + "Defined in file: %s:%d\r\n" + "Command: '%s'", + _FILENAME(sh_reg->file), + sh_reg->line, + cmd->cmd); + } + return -1; + } + const sh_cmd_t *test_cmd = &cmd[i]; - strcpy(err_cmd, " "); - strcpy(&err_cmd[1], test_cmd->cmd); + strcat(err_cmd, " "); + strcat(err_cmd, test_cmd->cmd); if (test_cmd->cmd_func == NULL) // 需要子命令 { if (test_cmd->fn_type == _SH_SUB_TYPE_SUB) { - if (_register_cmd_check_fn(&err_cmd[strlen(err_cmd)], test_cmd->sub_fn.sub_cmd) != 0) // 递归查检子命令 + if (_register_cmd_check_fn(sh_hdl, sh_reg, err_cmd, test_cmd->sub_fn.sub_cmd) != 0) // 递归查检子命令 { return -1; } } else { - if (test_cmd->fn_type != _SH_SUB_TYPE_VOID || test_cmd->sub_fn.nul != NULL) + if (test_cmd->fn_type == _SH_SUB_TYPE_CPFN) { - return -1; + SYS_LOG_WRN("sub_fn: pointer type mismatch in conditional expression!\r\n" + "Command:%s\r\n" + "Defined in file: %s:%d\r\n", + err_cmd, + _FILENAME(sh_reg->file), + sh_reg->line); } } } else // 是最终命令 { - if (test_cmd->fn_type != _SH_SUB_TYPE_CPFN) + if (test_cmd->fn_type == _SH_SUB_TYPE_SUB) { - if (test_cmd->fn_type != _SH_SUB_TYPE_VOID || test_cmd->sub_fn.nul != NULL) + SYS_LOG_WRN("sub_fn: pointer type mismatch in conditional expression!\r\n" + "Command:%s\r\n" + "Defined in file: %s:%d\r\n", + err_cmd, + _FILENAME(sh_reg->file), + sh_reg->line); + } + else + { + uint16_t save_depth[_MAX_SAVE_DEPTH] = {0}; + int argc; + const char *argv[CONFIG_SH_MAX_PARAM] = {0}; + char cmd_param[strlen(err_cmd) + 1]; + + sys_pslist_t new_cmd_list; + sys_pslist_init(&new_cmd_list); + sys_pslist_append(&new_cmd_list, &sh_reg->reg_node); + + /* 解析参数并输出 argc, argv, cmd_param */ + _parse_input(&argc, argv, cmd_param, err_cmd); + sh_cmd_info_t cmd_info_first = _find_registered_command(sh_hdl, &new_cmd_list, save_depth, argc, argv); + if (cmd_info_first.match_cmd != test_cmd) { - return -1; + sh_cmd_info_t cmd_info_new = _find_registered_command(sh_hdl, &new_cmd_list, save_depth, argc, argv); + if (cmd_info_new.match_status != _SH_CMD_STATUS_NotFound) + { + char cmd_name[strlen(err_cmd) + 1]; + cmd_name[0] = '\0'; + for (int i = 0; i < (cmd_info_first.cmd_count < cmd_info_new.cmd_count ? cmd_info_first.cmd_count : cmd_info_new.cmd_count); i++) + { + if (i > 0) + { + strcat(cmd_name, " "); + } + strcat(cmd_name, argv[i]); + } + SYS_LOG_WRN("cmd_func: repeatedly defined commands: '%s'\r\n" + "Defined in file: %s:%d\r\n" + "first defined here: %s:%d\r\n", + cmd_name, + _FILENAME(cmd_info_new.reg_struct->file), + cmd_info_new.reg_struct->line, + _FILENAME(cmd_info_first.reg_struct->file), + cmd_info_first.reg_struct->line); + } } } } + *err_cmd_bak = '\0'; } - err_cmd[0] = '\0'; return 0; } @@ -355,7 +466,7 @@ static void _insert_str(sh_t *sh_hdl, const char *str) int len = strlen(str); if (len) { - int remain = sizeof(sh_hdl->cmd_line) - (sh_hdl->cmd_buf - sh_hdl->cmd_line) - sh_hdl->cmd_stored - 1; + int remain = _SH_ARRAY_COUNT(sh_hdl->cmd_line) - (sh_hdl->cmd_buf - sh_hdl->cmd_line) - sh_hdl->cmd_stored - 1; if (len > remain) { len = remain; @@ -369,10 +480,14 @@ static void _insert_str(sh_t *sh_hdl, const char *str) } for (int i = sh_hdl->cmd_stored - 1 + len; i > sh_hdl->cmd_input; i--) /* 使光标之后的数据往后移动 len 个字节 */ + { sh_hdl->cmd_buf[i] = sh_hdl->cmd_buf[i - len]; + } for (int i = 0; i < len; i++) + { sh_hdl->cmd_buf[sh_hdl->cmd_input + i] = str[i]; + } sh_hdl->cmd_input += len; sh_hdl->cmd_stored += len; @@ -397,9 +512,11 @@ static void _insert_str(sh_t *sh_hdl, const char *str) * @param argv[out] 保存每个参数的地址(指向dest) * @param dest[out] 分离出来的参数字符串的副本,注意参数的使用期间需要保留。 * @param src 已记录的命令行数据 + * @return char* dest 的下一个可用地址 */ -static void _read_param(int *argc, const char *argv[CONFIG_SH_MAX_PARAM], char *dest, const char *src) +static char *_parse_input(int *argc, const char *argv[], char *dest, const char *src) { + char *ret = dest; bool end = false; *argc = 0; while (end == false && *argc < CONFIG_SH_MAX_PARAM) @@ -507,14 +624,17 @@ static void _read_param(int *argc, const char *argv[CONFIG_SH_MAX_PARAM], char * argv[*argc] = dest; ++(*argc); - dest = &dest[len + 1]; src = &src[len]; + dest = &dest[len + 1]; + ret = dest; } else { break; } } + + return ret; } /** @@ -633,20 +753,6 @@ static void _store_cmd_bak(sh_t *sh_hdl, const char *line, int pos, int n) } } -/** - * @brief 使剩余的字符串指针指向空字符串 - * - * @param argc 参数数量 - * @param argv 参数字符串指针 - */ -static void _clear_argv(int argc, const char *argv[]) -{ - for (int i = argc; i < CONFIG_SH_MAX_PARAM; i++) - { - argv[i] = NULL; - } -} - /** * @brief 获取当前提示符(不含模块提示符) * @@ -723,8 +829,8 @@ int sh_register_cmd_hide(const sh_cmd_reg_t *sh_reg) int sh_unregister_cmd(const sh_cmd_reg_t *sh_reg) { bool ret = (sh_reg == NULL); - ret = (ret != false ? ret : sys_pslist_find_and_remove(&s_cmd_list, &sh_reg->node)); - ret = (ret != false ? ret : sys_pslist_find_and_remove(&s_cmd_list_hide, &sh_reg->node)); + ret = (ret != false ? ret : sys_pslist_find_and_remove(&s_cmd_list, &sh_reg->reg_node)); + ret = (ret != false ? ret : sys_pslist_find_and_remove(&s_cmd_list_hide, &sh_reg->reg_node)); return -!ret; } @@ -750,11 +856,13 @@ int sh_register_key(sh_t *sh_hdl, const sh_key_reg_t *sh_key) if (_find_key(sh_hdl, sh_key->key->code)) { if (sh_hdl->disable_echo == 0) + { SYS_LOG_WRN("Repeat key code: '%s'\r\n", sh_key->key->code); + } return -1; } - sys_pslist_append(&sh_hdl->key_list, &sh_key->node); + sys_pslist_append(&sh_hdl->key_list, &sh_key->reg_node); return 0; } @@ -781,7 +889,7 @@ int sh_register_key_cmd(sh_t *sh_hdl, const sh_cmd_reg_t *sh_reg) */ int sh_unregister_key(sh_t *sh_hdl, const sh_key_reg_t *sh_key) { - return -!sys_pslist_find_and_remove(&sh_hdl->key_list, &sh_key->node); + return -!sys_pslist_find_and_remove(&sh_hdl->key_list, &sh_key->reg_node); } /** @@ -793,7 +901,7 @@ int sh_unregister_key(sh_t *sh_hdl, const sh_key_reg_t *sh_key) */ int sh_unregister_key_cmd(sh_t *sh_hdl, const sh_cmd_reg_t *sh_reg) { - return -!sys_pslist_find_and_remove(&sh_hdl->cmd_list, &sh_reg->node); + return -!sys_pslist_find_and_remove(&sh_hdl->cmd_list, &sh_reg->reg_node); } /** @@ -876,7 +984,7 @@ void sh_putstr(sh_t *sh_hdl, const char *str) { SYS_ASSERT(sh_hdl != NULL, ""); - char buf[sizeof(sh_hdl->cmd_line)]; + char buf[_SH_ARRAY_COUNT(sh_hdl->cmd_line)]; const char *from = str; char *to = buf; for (int i = 0; i < sizeof(buf);) @@ -1432,98 +1540,178 @@ void sh_refresh_line(sh_t *sh_hdl) */ static sh_cp_info_t _completion_init(const char *cmd) { - sh_cp_info_t info = { + sh_cp_info_t cp_info = { .arg_str = cmd, // 待分析的参数 .arg_len = strlen(cmd), // 待解析命令长度 .match_num = 0, // 可打印的列表的条目数 .list_algin = 0, // 确定格式中 %-*s 的 * 的值 }; - info.match_str[0] = '\0'; // 保存相同的字符部分 - return info; + cp_info.match_str[0] = '\0'; // 保存相同的字符部分 + return cp_info; } /** - * @brief 自动补全功能用:每被执行一次都被更新一次补全信息或直接打印可选项。 + * @brief 自动补全功能用:每被执行一次都被更新一次补全信息到 cp_info 或根据 cp_info 直接打印可选项。 * - * @param info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 - * @param sub_cmd 包含所有可选项的父节点。 - * @param print_match false -- 只刷新 info; true -- 只打印匹配的备选项。 - * @param max_lines print_match 为 true 时,可打印的选项数超过这个设定时,仅列出匹配的命令(不打印帮助信息)。 - * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的可选项。 + * @param cp_info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 + * @param sub_cmd 包含所有可选项的父节点。 + * @param suffix_space 补全后是否自动添加空格。如果补全的是命令,则添加空格;如果补全的是参数,则不添加空格,可最大程度保留用户输入的空格。 + * @param update_type 更新方法 + * @param unique_commands_buf NULL -- 只刷新 cp_info; !NULL -- 只打印匹配的备选项: 用于保存已经打印的备选项,避免重复打印,成员数量为 cp_info->match_num, 仅在 update_type 为 true 时有效。 + * @param max_lines update_type 为 _CP_UPDATE_TYPE_PRINT 时,可打印的选项数超过这个设定时,仅列出匹配的命令(不打印帮助信息)。 + * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的可选项。 + * @param update_cb 用于 tree 命令,当 !NULL 时,仅执行回调函数。 + * @param prefix 仅在 update_cb 为 !NULL 时有效,用于在打印时添加前缀。 */ -static void _completion_update(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cmd_t *sub_cmd, bool print_match, int max_lines, int flag_parent) +static void _completion_update( + sh_t *sh_hdl, + sh_cp_info_t *cp_info, + const sh_cmd_t *sub_cmd, + bool suffix_space, + __cp_update_type_t update_type, + const char *unique_commands_buf[], + int max_lines, + int flag_parent, + __cp_update_cb update_cb, + char *prefix, + int argc, + const char **argv) { - int list_algin = 100 / _calc_list_algin(info); - - if ((sub_cmd->cmd_func != NULL || sub_cmd->sub_fn.sub_cmd != NULL) && - (info->arg_len == 0 || strncmp(sub_cmd->cmd, info->arg_str, info->arg_len) == 0)) + if (cp_info->arg_len > _SH_ARRAY_COUNT(cp_info->match_str) - 1) { - if (flag_parent && sub_cmd->cmd_func) // 只按父命令更新 - { - return; - } + return; // 这将超出 CONFIG_SH_MAX_CMD_LEN 的限制 + } - if (print_match == false) // 更新补全信息 - { - int test_len = strlen(sub_cmd->cmd); - if (info->list_algin < test_len) - info->list_algin = test_len; + // 如果只按父命令更新且这是一个最终命令,则跳过 + if (flag_parent && sub_cmd->cmd_func) + { + return; + } - if (info->match_num) + if ((sub_cmd->cmd_func != NULL || sub_cmd->sub_fn.sub_cmd != NULL) && // 有效的父命令或最终命令函数 + (cp_info->arg_len == 0 || strncmp(sub_cmd->cmd, cp_info->arg_str, cp_info->arg_len) == 0)) + { + if (update_type == _CP_UPDATE_TYPE_UPDATE) // 更新补全信息 + { + if (update_cb == NULL) { - for (int i = 0; info->match_str[i] != '\0'; i++) + /* 根据参数 suffix_space 自动为候选命令添加空格为结尾 */ + int str_len = strlen(sub_cmd->cmd); + char _cmd[str_len + 2]; + strcpy(_cmd, sub_cmd->cmd); + if (suffix_space) { - if (info->match_str[i] != sub_cmd->cmd[i]) + if (str_len > 0 && _cmd[str_len - 1] != ' ') { - info->match_str[i] = '\0'; - break; + _cmd[str_len] = ' '; + _cmd[str_len + 1] = '\0'; } } - } - else - { - strcpy(info->match_str, sub_cmd->cmd); - } - info->match_num++; - } - else // 打印匹配的命令 - { - info->print_count++; + int test_len = strlen(_cmd); + if (cp_info->list_algin < test_len) + cp_info->list_algin = test_len; - const char *color_begin = ""; - const char *color_end = ""; - if (sub_cmd->cmd_func) - { - if (sub_cmd->sub_fn.cp_fn) // 有参数 + if (cp_info->match_num) { - color_begin = _COLOR_C; - color_end = _COLOR_END; - } - } - else // 有子命令 - { - color_begin = _COLOR_P; - color_end = _COLOR_END; - } - sh_echo(sh_hdl, "%s%-*s%s", color_begin, _calc_list_algin(info), sub_cmd->cmd, color_end); - - if (info->match_num > max_lines) // 超过 max_lines 个匹配项时,不再显示帮助信息 - { - if (info->print_count >= info->match_num || info->print_count % list_algin == 0) - { - sh_echo(sh_hdl, "\r\n"); - } - } - else - { - if (sub_cmd->help && *sub_cmd->help != '\0') - { - sh_echo(sh_hdl, "-- %s\r\n", sub_cmd->help); + for (int i = 0; cp_info->match_str[i] != '\0'; i++) + { + if (cp_info->match_str[i] != _cmd[i]) + { + cp_info->match_str[i] = '\0'; + break; + } + } } else { - sh_echo(sh_hdl, "\r\n"); + strcpy(cp_info->match_str, _cmd); + } + } + + cp_info->match_num++; + } + else // 打印匹配的命令 + { + // 如果有匹配缓存,且当前命令不在缓存中,则跳过 + if (unique_commands_buf && cp_info->match_num > 0) + { + for (int i = 0; i < cp_info->print_count; i++) + { + if (strcmp(sub_cmd->cmd, unique_commands_buf[i]) == 0) + { + if (update_type == _CP_UPDATE_TYPE_TEST) + { + cp_info->match_num--; + } + return; + } + } + unique_commands_buf[cp_info->print_count] = sub_cmd->cmd; + } + + cp_info->print_count++; + + if (update_type == _CP_UPDATE_TYPE_PRINT) // 打印补全信息 + { + if (update_cb == NULL) + { + const char *color_begin = ""; + const char *color_end = ""; + if (sub_cmd->cmd_func) // 最终命令 + { + if (sub_cmd->fn_type == _SH_SUB_TYPE_CPFN) // 带参数自动补全的命令 + { + color_begin = _COLOR_C; + color_end = _COLOR_END; + } + } + else // 有子命令 + { + color_begin = _COLOR_P; + color_end = _COLOR_END; + } + + if (cp_info->arg_len > 0) + { + // 修改打印方式,分段显示:已输入部分用低亮显示,剩余部分正常显示 + sh_echo(sh_hdl, "\033[2m%.*s\033[0m", cp_info->arg_len, sub_cmd->cmd); + } + + sh_echo(sh_hdl, "%s%-*s%s", color_begin, _calc_list_algin(cp_info) - cp_info->arg_len, &sub_cmd->cmd[cp_info->arg_len], color_end); + + if (cp_info->match_num > max_lines) // 超过 max_lines 个匹配项时,不再显示帮助信息 + { + int list_algin = 100; + if (cp_info->list_algin > 0) + { + list_algin = 100 / cp_info->list_algin; + if (list_algin < 1) + { + list_algin = 1; + } + } + + if (cp_info->print_count >= cp_info->match_num || cp_info->print_count % list_algin == 0) + { + sh_echo(sh_hdl, "\r\n"); + } + } + else + { + if (sub_cmd->help && *sub_cmd->help != '\0') + { + sh_echo(sh_hdl, "-- %s\r\n", sub_cmd->help); + } + else + { + sh_echo(sh_hdl, "\r\n"); + } + } + } + else + { + update_cb(sh_hdl, cp_info, sub_cmd, prefix, argc, argv); } } } @@ -1533,115 +1721,163 @@ static void _completion_update(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cmd_t /** * @brief 自动补全功能用:执行一次自动补全命令或打印备选命令的功能 * - * @param info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 - * @param cmd_info 包含所有备选项的父节点的原始数据结构。 - * @param print_match false -- 仅尝试自动补全命令; true -- 只打印匹配的备选项。 - * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的备选命令。 + * @param cp_info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 + * @param cp_param 补全参数的结构 + * @param print_match false -- 仅尝试自动补全命令; true -- 只打印匹配的备选项。 + * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的备选命令。 + * @param update_cb 用于 tree 命令,当 !NULL 时,仅执行回调函数。 + * @param prefix 仅在 update_cb 为 !NULL 时有效,用于在打印时添加前缀。 * @return true 当 print_match 为 false (仅尝试自动补全命令)时,成功执行了一次补全命令的动作 * @return false 当 print_match 为 true (只打印匹配的备选项)或无法补全命令 */ -static bool _do_completion_cmd(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cmd_info_t *cmd_info, bool print_match, int flag_parent) +static bool _do_completion_cmd(sh_t *sh_hdl, + sh_cp_info_t *cp_info, + __cp_param_t *cp_param, + bool print_match, + int flag_parent, + __cp_update_cb update_cb, + char *prefix) { - char new_cmd[sizeof(sh_hdl->cmd_line)]; - const sh_cmd_t *sub_cmd; - int err_status; + int match_num = 0; + const char *unique_commands_buf[cp_info->match_num]; + int argc = cp_param->argc; + const char **argv = cp_param->argv; + bool try_sub_commands = !!cp_param->argc; + __cp_update_type_t update_type = print_match ? _CP_UPDATE_TYPE_TEST : _CP_UPDATE_TYPE_UPDATE; - if (print_match) + do { - if (info->match_num == 0) + if (print_match) { - return false; - } - - if (sh_hdl->cp_operate != SH_CP_OP_PEND) - { - sh_hdl->cp_operate = SH_CP_OP_PEND; - sh_echo(sh_hdl, "\r\n"); - } - info->print_count = 0; - } - else - { - info->match_num = 0; - } - - if (cmd_info) - { - sub_cmd = cmd_info->match_cmd; - err_status = cmd_info->err_status; - } - else - { - sub_cmd = sh_hdl->select_cmd; - err_status = !_SH_CMD_STATUS_Incomplete; - } - if (sub_cmd == NULL) - { - sub_cmd = sh_hdl->select_cmd; - } - - while (1) - { - if (sub_cmd == NULL) // 遍历根命令链 - { - const sh_cmd_reg_t *sh_reg; - sys_pslist_t *cmd_list[] = { - &s_cmd_list, - &sh_hdl->cmd_list, - }; - for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_list[0]); i++) + if (cp_info->match_num == 0) { - sys_pslist_t *test_list = cmd_list[i]; - SYS_PSLIST_FOR_EACH_CONTAINER(test_list, sh_reg, node) + return false; + } + + if (match_num == cp_info->match_num || cp_info->print_count != 0) + { + update_type = _CP_UPDATE_TYPE_PRINT; + + if (update_cb == NULL) { - const sh_cmd_t *reg_cmd = sh_reg->cmd; - for (int i = 0; *(int *)®_cmd[i] != 0; i++) + if (sh_hdl->cp_operate != SH_CP_OP_PEND) { - sh_cmd_t new_data = reg_cmd[i]; - new_data.cmd = new_cmd; - strcpy(new_cmd, reg_cmd[i].cmd); - strcat(new_cmd, " "); - _completion_update(sh_hdl, info, &new_data, print_match, 30, flag_parent); + sh_hdl->cp_operate = SH_CP_OP_PEND; + if (update_cb == NULL) + { + sh_echo(sh_hdl, "\r\n"); + } } } } - } - else // 遍历子命令数据 - { - sub_cmd = sub_cmd->sub_fn.sub_cmd; - for (int i = 0; sub_cmd[i].cmd != NULL; i++) - { - sh_cmd_t new_data = sub_cmd[i]; - new_data.cmd = new_cmd; - strcpy(new_cmd, sub_cmd[i].cmd); - strcat(new_cmd, " "); - _completion_update(sh_hdl, info, &new_data, print_match, 30, flag_parent); - } - if (sh_hdl->select_cmd != NULL && // 当前状态为模块模式 - info->print_count == 0 && // 没有打印任何可选项 - err_status != _SH_CMD_STATUS_Incomplete && // 如果正在输出当前模块下的子命令,则只继续补全子命令 - print_match == true && // 只打印匹配的备选项 - flag_parent == 0 // 目前仅在 _cmd_select_param() 被置起,此时不再遍历根命令链 - ) - { - sub_cmd = NULL; // 使从根命令链重新查找 - continue; - } - } + match_num = cp_info->match_num; - if (sh_hdl->select_cmd != NULL && // 当前状态为模块模式 - info->match_num == 0 && // 没有找到可匹配的命令 - err_status != _SH_CMD_STATUS_Incomplete && // 如果正在输出当前模块下的子命令,则只继续补全子命令 - sub_cmd != NULL // 未遍历过根命令链 - ) - { - sub_cmd = NULL; // 使从根命令链重新查找 + cp_info->print_count = 0; } else { - break; + cp_info->match_num = 0; } + + for (int i = 0; i < 1 + !!sh_hdl->select_argc; i++) // 因为有模块模式的存在,有可能需要重试查找,需要两次循环 + { + if (try_sub_commands) + { + int match_num = cp_info->match_num; // 可打印的列表的条目数 + int print_count = cp_info->print_count; // 已打印计算 + + /* 遍历子命令数据 */ + memset(cp_param->save_depth, 0, sizeof(cp_param->save_depth)); + while (1) + { + sh_cmd_info_t cmd_info = _find_registered_command(sh_hdl, NULL, cp_param->save_depth, argc, argv); + const sh_cmd_t *sub_cmd = cmd_info.match_cmd; + if (sub_cmd != NULL) + { + if (sub_cmd->fn_type == _SH_SUB_TYPE_SUB) + { + sub_cmd = sub_cmd->sub_fn.sub_cmd; + for (int i = 0; sub_cmd[i].cmd != NULL; i++) + { + _completion_update(sh_hdl, + cp_info, + &sub_cmd[i], + true, + update_type, + unique_commands_buf, + _MAX_DETAIL_LINES, + flag_parent, + update_cb, + prefix, + argc, + argv); + } + } + } + else + { + break; + } + } + + if ((cp_info->match_num != match_num) || (cp_info->print_count != print_count)) // 有能匹配的命令 + { + break; + } + + if (sh_hdl->select_argc == 0) // 当前状态不是模块模式,不需要重试查找 + { + break; + } + + if (cp_param->argc != sh_hdl->select_argc) // 在模块模式下,最少已输入一个的命令段,不再继续查找 + { + break; + } + + try_sub_commands = false; // 当前状态为模块模式,正在输入第一个命令,但这个命令没有在已选择的命令中找到,需要重试查找 + } + else // !try_sub_commands + { + /* 遍历根命令链 */ + sys_pslist_t *cmd_lists[] = { + &sh_hdl->cmd_list, + &s_cmd_list, + }; + for (int i = 0; i < _SH_ARRAY_COUNT(cmd_lists); i++) + { + sys_pslist_t *test_list = cmd_lists[i]; + const sh_cmd_reg_t *sh_reg; + SYS_PSLIST_FOR_EACH_CONTAINER(test_list, sh_reg, reg_node) + { + const sh_cmd_t *sh_cmd = sh_reg->cmd; + for (int i = 0; *(int *)&sh_cmd[i] != 0; i++) + { + _completion_update(sh_hdl, + cp_info, + &sh_cmd[i], + true, + update_type, + unique_commands_buf, + _MAX_DETAIL_LINES, + flag_parent, + update_cb, + prefix, + argc, + argv); + } + } + } + + break; + } + } + } while (update_type == _CP_UPDATE_TYPE_TEST); + + if (update_cb) + { + return false; } if (print_match) @@ -1649,23 +1885,25 @@ static bool _do_completion_cmd(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cmd_in return false; } - return _do_completion_insert(sh_hdl, info); + return _do_completion_insert(sh_hdl, cp_info); } /** * @brief 自动补全功能用:执行一次自动补全参数或打印备选参数的功能 * - * @param info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 - * @param sh_param 包含所有备选项的参数的结构。注意结构体必须有值为 NULL 的成员为结束。 - * @param print_match false -- 仅尝试自动补全参数; true -- 只打印匹配的备选项。 + * @param cp_info[out] 由 _completion_init() 初始化的补全信息指针,并被实时记录刷新。 + * @param sh_param 包含所有备选项的参数的结构。注意结构体必须有值为 NULL 的成员为结束。 + * @param print_match false -- 仅尝试自动补全参数; true -- 只打印匹配的备选项。 * @return true 当 print_match 为 false (仅尝试自动补全参数)时,成功执行了一次补全参数的动作 * @return false 当 print_match 为 true (只打印匹配的备选项)或无法补全参数 */ -static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cp_param_t *sh_param, bool print_match) +static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *cp_info, const sh_cp_param_t *sh_param, bool print_match) { + const char *unique_commands_buf[cp_info->match_num]; + if (print_match) { - if (info->match_num == 0) + if (cp_info->match_num == 0) { return false; } @@ -1675,25 +1913,34 @@ static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cp_p sh_echo(sh_hdl, "\r\n"); sh_hdl->cp_operate = SH_CP_OP_PEND; } - info->print_count = 0; + cp_info->print_count = 0; } else { - info->match_num = 0; + cp_info->match_num = 0; } for (int i = 0; sh_param[i].cmd != NULL; i++) { - char new_cmd[sizeof(sh_hdl->cmd_line)]; sh_cmd_t sub_cmd = { - .cmd = new_cmd, + .cmd = sh_param[i].cmd, .help = sh_param[i].help, .cmd_func = (__typeof(sub_cmd.cmd_func))1, - .sub_fn = {.sub_cmd = NULL}, + .sub_fn = {.nul = NULL}, + .fn_type = _SH_SUB_TYPE_NUL, }; - strcpy(new_cmd, sh_param[i].cmd); - strcat(new_cmd, " "); - _completion_update(sh_hdl, info, &sub_cmd, print_match, 30, 0); + _completion_update(sh_hdl, + cp_info, + &sub_cmd, + false, + print_match ? _CP_UPDATE_TYPE_PRINT : _CP_UPDATE_TYPE_UPDATE, + unique_commands_buf, + _MAX_DETAIL_LINES, + 0, + NULL, + NULL, + 0, + NULL); } if (print_match) @@ -1701,26 +1948,30 @@ static bool _do_completion_param(sh_t *sh_hdl, sh_cp_info_t *info, const sh_cp_p return false; } - return _do_completion_insert(sh_hdl, info); + return _do_completion_insert(sh_hdl, cp_info); } /** * @brief 自动补全功能用:执行补全 * - * @param info[out] 由 _completion_init() 初始化的补全信息指针 + * @param cp_info[out] 由 _completion_init() 初始化的补全信息指针 * @return true 成功执行了一次补全的动作 * @return false 未执行补全的动作 */ -static bool _do_completion_insert(sh_t *sh_hdl, sh_cp_info_t *info) +static bool _do_completion_insert(sh_t *sh_hdl, sh_cp_info_t *cp_info) { /* 执行自动补全,返回:显示是否被自动更新 */ - if (info->match_str[info->arg_len] == '\0') + if (cp_info->match_str[cp_info->arg_len] == '\0') { + if ((cp_info->match_num | cp_info->print_count) == 0) + { + sh_hdl->cp_operate = SH_CP_OP_ERR; + } return false; } else { - _insert_str(sh_hdl, &info->match_str[info->arg_len]); + _insert_str(sh_hdl, &cp_info->match_str[cp_info->arg_len]); sh_hdl->cp_operate = SH_CP_OP_CP; return true; } @@ -1743,16 +1994,16 @@ static void _do_restore_line(sh_t *sh_hdl) /** * @brief 确定每个命令或参数的对齐的长度 * - * @param info[out] 由 _completion_update() 更新的补全信息指针 + * @param cp_info[out] 由 _completion_update() 更新的补全信息指针 * @return int 每个命令或参数应占用的打印空间 */ -static int _calc_list_algin(sh_cp_info_t *info) +static int _calc_list_algin(sh_cp_info_t *cp_info) { - return (info->list_algin > 8 ? info->list_algin : 8) + 2; + return (cp_info->list_algin > 8 ? cp_info->list_algin : 8) + 2; } /** - * @brief 完整解析命令并获取命令信息 + * @brief 完整解析命令并获取命令信息。注意不管是不是在模块模式下,都需要解析完整的命令 * * @param argc 命令数量 * @param argv 命令字符串指针 @@ -1761,91 +2012,95 @@ static int _calc_list_algin(sh_cp_info_t *info) sh_cmd_info_t sh_cmd_test(sh_t *sh_hdl, int argc, const char *argv[]) { SYS_ASSERT(sh_hdl != NULL, ""); - - unsigned cmd_sections = 0; - sh_cmd_info_t info; - - info.err_status = _SH_CMD_STATUS_Bad; - info.match_count = 0; - info.reg_struct = NULL; - info.match_cmd = NULL; - - for (cmd_sections = 0; cmd_sections < argc; cmd_sections++) // 依次查找全部子命令 - { - info.match_cmd = _find_cmd(sh_hdl, &info.reg_struct, info.match_cmd, argv[cmd_sections]); - - if (info.match_cmd == NULL) - { - info.err_status = _SH_CMD_STATUS_Bad; - info.match_count = cmd_sections; - return info; - } - - if (info.match_cmd->cmd_func) // 找到子命令 - { - info.err_status = _SH_CMD_STATUS_Success; - info.match_count = cmd_sections + 1; - return info; - } - - if (cmd_sections + 1 < argc) - { - info.match_cmd = info.match_cmd->sub_fn.sub_cmd; - } - } - - if (info.match_cmd && info.match_cmd->sub_fn.sub_cmd && cmd_sections == argc) - { - info.err_status = _SH_CMD_STATUS_Incomplete; - info.match_count = cmd_sections; - return info; - } - - return info; + uint16_t save_depth[_MAX_SAVE_DEPTH] = {0}; + return _find_registered_command(sh_hdl, NULL, save_depth, argc, argv); } /** * @brief 根据输入的参数,结合当前命令行的状态,在已注册命令中查找并自执行一次自动补全命令/打印备选命令的全过程 * - * @param argc 命令数量 - * @param argv 命令字符串指针 - * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的可选项。 + * @param argc 命令数量 + * @param argv 命令字符串指针 + * @param flag_parent 仅在内部的命令 select 中置 1 使用,表示只包含父命令的可选项。 + * @param update_cb 用于 tree 命令,当 !NULL 时,仅执行回调函数。 + * @param prefix 仅在 update_cb 为 !NULL 时有效,用于在打印时添加前缀。 * @return sh_cp_op_t 自动补全命令/打印备选命令的执行结果 */ -static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], int flag_parent) +static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], int flag_parent, __cp_update_cb update_cb, char *prefix) { sh_cmd_info_t cmd_info; sh_cp_info_t cp_info; + __cp_param_t cp_param = { + .argc = argc, + .argv = argv, + .save_depth = {0}, + }; + bool arg_is_end = _arg_is_end(sh_hdl); + + if (update_cb) + { + arg_is_end = true; + } if (sh_hdl->exec_flag == 0) { sh_hdl->cp_operate = SH_CP_OP_NA; } - if (argc) + /* 初始化 cp_info 和 cmd_info */ + cp_info = _completion_init(""); + if (sh_hdl->select_argc != 0) { - /* 初始化 cmd_info 和 cp_info */ - if (_arg_is_end(sh_hdl)) + argv = cp_param.argv - sh_hdl->select_argc; + const char *cmd_line = sh_hdl->cmd_line; + for (int i = 0; i < sh_hdl->select_argc; i++) { - cmd_info = sh_cmd_test(sh_hdl, argc, argv); - cp_info = _completion_init(""); + argv[i] = cmd_line; + cmd_line = &cmd_line[strlen(cmd_line) + 1]; + } + argc += sh_hdl->select_argc; + cmd_info = _find_registered_command(sh_hdl, NULL, cp_param.save_depth, argc > 0 ? argc - !arg_is_end : 0, argv); + if (cmd_info.match_status == _SH_CMD_STATUS_NotFound) + { + memset(cp_param.save_depth, 0, sizeof(cp_param.save_depth)); + argc = cp_param.argc; + argv = cp_param.argv; + cmd_info = _find_registered_command(sh_hdl, NULL, cp_param.save_depth, argc > 0 ? argc - !arg_is_end : 0, argv); } else { - cmd_info = sh_cmd_test(sh_hdl, --argc, argv); - if (cmd_info.err_status == _SH_CMD_STATUS_Success) + cp_param.argc = argc; + cp_param.argv = argv; + } + } + else + { + cmd_info = _find_registered_command(sh_hdl, NULL, cp_param.save_depth, argc > 0 ? argc - !arg_is_end : 0, argv); + } + + if (cmd_info.match_status == _SH_CMD_STATUS_NotFound) // 命令错误 + { + return sh_hdl->cp_operate; + } + + if (argc) + { + if (!arg_is_end) + { + if (cmd_info.match_status == _SH_CMD_STATUS_Success) { - cp_info = _completion_init(argv[argc++]); + cp_info = _completion_init(argv[argc - 1]); } else { - cp_info = _completion_init(argv[cmd_info.match_count]); + cp_info = _completion_init(argv[cmd_info.cmd_count]); } + cp_param.argc = argc - 1; } - if (cmd_info.err_status == _SH_CMD_STATUS_Success) + if (cmd_info.match_status == _SH_CMD_STATUS_Success) // 完整的命令 { - if (cmd_info.match_cmd->sub_fn.cp_fn != NULL) + if (cmd_info.match_cmd->fn_type == _SH_SUB_TYPE_CPFN) // 带参数自动补全的命令 { do { @@ -1857,7 +2112,7 @@ static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], cp_info.list_algin = sh_hdl->cp_list_algin; if (cp_info.match_num < 2) { - break; + return sh_hdl->cp_operate; } if (sh_hdl->cp_operate != SH_CP_OP_PEND) @@ -1871,8 +2126,7 @@ static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], ++sh_hdl->exec_flag; sh_hdl->cp_info = &cp_info; - _clear_argv(argc, argv); - cmd_info.match_cmd->sub_fn.cp_fn(sh_hdl, argc - cmd_info.match_count, &argv[cmd_info.match_count], _arg_is_end(sh_hdl)); + cmd_info.match_cmd->sub_fn.cp_fn(sh_hdl, argc - cmd_info.cmd_count, &argv[cmd_info.cmd_count], arg_is_end); sh_hdl->cp_info = NULL; --sh_hdl->exec_flag; @@ -1903,42 +2157,35 @@ static sh_cp_op_t _sh_completion_cmd(sh_t *sh_hdl, int argc, const char *argv[], return sh_hdl->cp_operate; // 包含完整的命令 } + } - if (cmd_info.match_count != argc) - { - return sh_hdl->cp_operate; // 中间的命令错误 - } + if (update_cb) + { + _do_completion_cmd(sh_hdl, &cp_info, &cp_param, false, flag_parent, update_cb, prefix); + _do_completion_cmd(sh_hdl, &cp_info, &cp_param, true, flag_parent, update_cb, prefix); } else { - cmd_info = sh_cmd_test(sh_hdl, argc, argv); - cp_info = _completion_init(""); // 没有任何命令时直接打印所有根命令 + if (_do_completion_cmd(sh_hdl, &cp_info, &cp_param, false, flag_parent, NULL, NULL) == false) + { + if (cp_info.match_num > 1) // 不需要自动补全 + { + if (sh_hdl->tab_press_count != 0) + { + /* 打印命令 */ + _do_completion_cmd(sh_hdl, &cp_info, &cp_param, true, flag_parent, NULL, NULL); + + /* 恢复当前命令行显示 */ + _do_restore_line(sh_hdl); + } + else + { + sh_hdl->tab_press_count = 1; + } + } + } } - do - { - if (_do_completion_cmd(sh_hdl, &cp_info, &cmd_info, false, flag_parent)) - { - break; // 因为命令行被自动更新,不打印可用命令 - } - if (cp_info.match_num == 0) - { - break; // 没有匹配的命令 - } - if (sh_hdl->tab_press_count == 0) - { - sh_hdl->tab_press_count = 1; - break; // 暂停一次操作 - } - - /* 打印命令 */ - _do_completion_cmd(sh_hdl, &cp_info, &cmd_info, true, flag_parent); - - /* 恢复当前命令行显示 */ - _do_restore_line(sh_hdl); - - } while (0); - return sh_hdl->cp_operate; } @@ -1964,7 +2211,9 @@ sh_cp_op_t sh_completion_param(sh_t *sh_hdl, const sh_cp_param_t *param) if (sh_hdl->cp_info == NULL) { if (sh_hdl->disable_echo == 0) + { SYS_LOG_WRN("This function is only used for functions with automatic parameter completion. @ref SH_SETUP_CMD\r\n"); + } break; } @@ -1973,25 +2222,24 @@ sh_cp_op_t sh_completion_param(sh_t *sh_hdl, const sh_cp_param_t *param) break; } - if (_do_completion_param(sh_hdl, sh_hdl->cp_info, param, false)) + if (_do_completion_param(sh_hdl, sh_hdl->cp_info, param, false) == false) { - break; // 因为命令行被自动更新,不打印可用命令 - } - if (sh_hdl->cp_info->match_num == 0) - { - break; // 没有匹配的命令 - } - if (sh_hdl->tab_press_count == 0) - { - sh_hdl->tab_press_count = 1; - break; // 暂停一次操作 - } + if (sh_hdl->cp_info->match_num > 1) // 不需要自动补全 + { + if (sh_hdl->tab_press_count != 0) + { + /* 打印参数 */ + _do_completion_param(sh_hdl, sh_hdl->cp_info, param, true); - /* 打印参数 */ - _do_completion_param(sh_hdl, sh_hdl->cp_info, param, true); - - /* 恢复当前命令行显示 */ - _do_restore_line(sh_hdl); + /* 恢复当前命令行显示 */ + _do_restore_line(sh_hdl); + } + else + { + sh_hdl->tab_press_count = 1; + } + } + } } while (0); @@ -2018,7 +2266,9 @@ void sh_completion_resource(sh_t *sh_hdl, const char *arg_str, const char *res_s { sh_hdl->cp_resource_flag = 1; if (sh_hdl->disable_echo == 0) + { SYS_LOG_WRN("This function is only used for functions with automatic parameter completion. @ref SH_SETUP_CMD\r\n"); + } } return; } @@ -2040,14 +2290,26 @@ void sh_completion_resource(sh_t *sh_hdl, const char *arg_str, const char *res_s .cmd = res_str, .help = help, .cmd_func = (__typeof(sub_cmd.cmd_func))1, - .sub_fn = {.sub_cmd = NULL}, + .sub_fn = {.nul = NULL}, + .fn_type = _SH_SUB_TYPE_NUL, }; int max_lines = 0; if (help != NULL && *help != '\0') { max_lines = ~(1u << (sizeof(max_lines) * 8 - 1)); } - _completion_update(sh_hdl, sh_hdl->cp_info, &sub_cmd, (bool)!!sh_hdl->tab_press_count, max_lines, 0); + _completion_update(sh_hdl, + sh_hdl->cp_info, + &sub_cmd, + false, + sh_hdl->tab_press_count ? _CP_UPDATE_TYPE_PRINT : _CP_UPDATE_TYPE_UPDATE, + NULL, + max_lines, + 0, + NULL, + NULL, + 0, + NULL); } /** @@ -2058,6 +2320,10 @@ void sh_completion_resource(sh_t *sh_hdl, const char *arg_str, const char *res_s */ static bool _arg_is_end(sh_t *sh_hdl) { + if (sh_hdl->cmd_input == 0) + { + return true; + } return (bool)(sh_ctrl_get_line(sh_hdl)[sh_hdl->cmd_input - 1] == ' '); } @@ -2101,18 +2367,19 @@ sh_cp_op_t sh_get_cp_result(sh_t *sh_hdl) void sh_ctrl_tab(sh_t *sh_hdl) { int argc; - const char *argv[CONFIG_SH_MAX_PARAM]; - char cmd_param[sizeof(sh_hdl->cmd_line)]; + const char *argv[CONFIG_SH_MAX_PARAM] = {0}; + const char **pargv = &argv[sh_hdl->select_argc]; + char cmd_param[_SH_ARRAY_COUNT(sh_hdl->cmd_line)]; /* 复制当前显示的命令到 cmd_param ,并在光标处截断 */ strcpy(cmd_param, sh_ctrl_get_line(sh_hdl)); cmd_param[sh_hdl->cmd_input] = '\0'; /* 解析参数并输出 argc, argv, cmd_param */ - _read_param(&argc, argv, cmd_param, cmd_param); + _parse_input(&argc, pargv, cmd_param, cmd_param); /* 自动补全命令 */ - _sh_completion_cmd(sh_hdl, argc, argv, 0); + _sh_completion_cmd(sh_hdl, argc, pargv, 0, NULL, NULL); } /** @@ -2124,35 +2391,59 @@ void sh_ctrl_tab(sh_t *sh_hdl) bool sh_ctrl_enter(sh_t *sh_hdl) { int argc; - const char *argv[CONFIG_SH_MAX_PARAM]; - char cmd_param[sizeof(sh_hdl->cmd_line)]; + const char *argv[CONFIG_SH_MAX_PARAM] = {0}; + const char **pargv = &argv[sh_hdl->select_argc]; + char cmd_param[_SH_ARRAY_COUNT(sh_hdl->cmd_line)]; bool ret = false; const char *cmd_line = sh_ctrl_get_line(sh_hdl); ++sh_hdl->exec_flag; /* 解析参数并输出 argc, argv, cmd_param */ - _read_param(&argc, argv, cmd_param, cmd_line); + _parse_input(&argc, pargv, cmd_param, cmd_line); + if (argc) { /* 将当前回翻的记录复制到缓存中 */ _apply_history(sh_hdl); - /* 根据 argc, argv,解析出 info */ - sh_cmd_info_t info = sh_cmd_test(sh_hdl, argc, argv); - if (info.err_status == _SH_CMD_STATUS_Success) + sh_cmd_info_t cmd_info; + + if (sh_hdl->select_argc != 0) { - _clear_argv(argc, argv); - sh_hdl->cmd_return = info.match_cmd->cmd_func(sh_hdl, argc - info.match_count, &argv[info.match_count]); + const char *cmd_line = sh_hdl->cmd_line; + pargv -= sh_hdl->select_argc; + for (int i = 0; i < sh_hdl->select_argc; i++) + { + pargv[i] = cmd_line; + cmd_line = &cmd_line[strlen(cmd_line) + 1]; + } + argc += sh_hdl->select_argc; + cmd_info = sh_cmd_test(sh_hdl, argc, pargv); + if (cmd_info.match_status == _SH_CMD_STATUS_NotFound) + { + argc -= sh_hdl->select_argc; + pargv = &argv[sh_hdl->select_argc]; + cmd_info = sh_cmd_test(sh_hdl, argc, pargv); + } + } + else + { + cmd_info = sh_cmd_test(sh_hdl, argc, pargv); + } + + if (cmd_info.match_status == _SH_CMD_STATUS_Success) + { + sh_hdl->cmd_return = cmd_info.match_cmd->cmd_func(sh_hdl, argc - cmd_info.cmd_count, &pargv[cmd_info.cmd_count]); ret = true; } else { for (int i = 0; i < argc; i++) { - sh_echo(sh_hdl, "%s%s", argv[i], i + 1 < argc ? " " : ""); + sh_echo(sh_hdl, "%s%s", pargv[i], i + 1 < argc ? " " : ""); } - if (info.err_status == _SH_CMD_STATUS_Incomplete) + if (cmd_info.match_status == _SH_CMD_STATUS_NeedsSubcommand) { sh_echo(sh_hdl, ": command not complete\r\n"); sh_hdl->cmd_return = 2; @@ -2451,10 +2742,15 @@ void sh_ctrl_print_cmd_line(sh_t *sh_hdl) { sh_echo(sh_hdl, _COLOR_G); sh_echo(sh_hdl, sh_ctrl_get_prompt(sh_hdl)); - if (sh_hdl->select_cmd) + if (sh_hdl->select_argc) { sh_echo(sh_hdl, _COLOR_P); - sh_echo(sh_hdl, "%s: ", sh_hdl->cmd_line); + const char *cmd_line = sh_hdl->cmd_line; + for (int i = 0; i < sh_hdl->select_argc; i++) + { + sh_echo(sh_hdl, "%s%s ", cmd_line, i + 1 >= sh_hdl->select_argc ? ":" : ""); + cmd_line = &cmd_line[strlen(cmd_line) + 1]; + } } sh_echo(sh_hdl, _COLOR_END); const char *line = sh_ctrl_get_line(sh_hdl); @@ -2563,7 +2859,7 @@ bool sh_ctrl_undelete(sh_t *sh_hdl) */ bool sh_ctrl_is_module(sh_t *sh_hdl) { - return (bool)(!!sh_hdl->select_cmd); + return (bool)(!!sh_hdl->select_argc); } /** @@ -2658,11 +2954,16 @@ char sh_ctrl_get_line_char(sh_t *sh_hdl, uint16_t pos) SH_CMD_FN(_cmd_print_init_fn) { + if (sh_hdl->disable_echo) + { + return 0; + } + extern sys_init_t __sys_init_leader_0; extern sys_init_t __sys_init_leader_e; sys_init_t *start = &__sys_init_leader_0; sys_init_t *end = &__sys_init_leader_e; - int match_count = 0; + int cmd_count = 0; sh_echo(sh_hdl, "Automatic initialization list >>>\r\n"); while (start < end) { @@ -2700,22 +3001,27 @@ SH_CMD_FN(_cmd_print_init_fn) if (result) { sh_echo(sh_hdl, "%s\r\n", buf); - match_count++; + cmd_count++; } } start = &start[1]; } - sh_echo(sh_hdl, "Total: %d\r\n", match_count); + sh_echo(sh_hdl, "Total: %d\r\n", cmd_count); return 0; } SH_CMD_FN(_cmd_print_module_struct) { + if (sh_hdl->disable_echo) + { + return 0; + } + extern const module_t __sys_module_leader_0; extern const module_t __sys_module_leader_9; const module_t *start = &__sys_module_leader_0; const module_t *end = &__sys_module_leader_9; - int match_count = 0; + int cmd_count = 0; sh_echo(sh_hdl, "Internal module cmd list >>>\r\n"); const struct @@ -2753,21 +3059,102 @@ SH_CMD_FN(_cmd_print_module_struct) if (result) { sh_echo(sh_hdl, "%s\r\n", buf); - match_count++; + cmd_count++; } } start = &start[1]; } } - sh_echo(sh_hdl, "Total: %d\r\n", match_count); + sh_echo(sh_hdl, "Total: %d\r\n", cmd_count); return 0; } #endif /* #if defined(MIX_SHELL) */ +static void _cmd_tree_recursive(sh_t *sh_hdl, sh_cp_info_t *cp_info, const sh_cmd_t *sub_cmd, char *prefix, int argc, const char **argv) +{ + bool next_is_last = cp_info->print_count >= cp_info->match_num; + + // 打印前缀 + sh_echo(sh_hdl, "%s", prefix); + + // 打印分支符号 + sh_echo(sh_hdl, "%s", next_is_last ? "└── " : "├── "); + + // 打印命令名称,使用颜色区分有子命令的命令 + if (sub_cmd->cmd_func == NULL && sub_cmd->fn_type == _SH_SUB_TYPE_SUB) + { + sh_echo(sh_hdl, "%s%s%s\r\n", _COLOR_P, sub_cmd->cmd, _COLOR_END); + + // 保存当前前缀 + size_t prefix_len = strlen(prefix); + + // 添加新的前缀用于下一层级 + strcat(prefix, next_is_last ? " " : "│ "); + + // 递归打印子命令 + argv[argc] = sub_cmd->cmd; + _sh_completion_cmd(sh_hdl, argc + 1, argv, 0, _cmd_tree_recursive, prefix); + + // 恢复原前缀 + prefix[prefix_len] = '\0'; + } + else if (sub_cmd->cmd_func != NULL) + { + if (sub_cmd->fn_type == _SH_SUB_TYPE_CPFN) + { + sh_echo(sh_hdl, "%s%s%s\r\n", _COLOR_C, sub_cmd->cmd, _COLOR_END); + } + else + { + sh_echo(sh_hdl, "%s\r\n", sub_cmd->cmd); + } + } + else + { + sh_echo(sh_hdl, "\033[2m%s\033[0m\r\n", sub_cmd->cmd); + } +} + +SH_CMD_FN(_cmd_tree) +{ + if (sh_hdl->disable_echo) + { + return 0; + } + + char prefix[CONFIG_SH_MAX_PARAM * 4 + 20] = {0}; + + uint16_t select_argc = sh_hdl->select_argc; + sh_hdl->select_argc = 0; + + if (argc == 0) + { + sh_echo(sh_hdl, "Global command\r\n"); + } + + // 打印收到的参数 + for (int i = 0; i < argc; i++) + { + sh_echo(sh_hdl, "%s%s%s", i ? "" : "'", argv[i], i + 1 < argc ? " " : "'\r\n"); + } + + // 打印命令树 + _sh_completion_cmd(sh_hdl, argc, argv, 0, _cmd_tree_recursive, prefix); + + sh_hdl->select_argc = select_argc; + + return 0; +} + SH_CMD_FN(_cmd_history) { + if (sh_hdl->disable_echo) + { + return 0; + } + if (argc == 0) { for (int i = sh_hdl->history_valid_num; i--;) @@ -2795,44 +3182,47 @@ SH_CMD_FN(_cmd_history) SH_CMD_FN(_cmd_select) { + uint16_t save_depth[_MAX_SAVE_DEPTH] = {0}; + if (argc) { - sh_cmd_info_t cmd_info = sh_cmd_test(sh_hdl, argc, argv); - if (cmd_info.err_status == _SH_CMD_STATUS_Incomplete) + sh_cmd_info_t cmd_info = _find_registered_command(sh_hdl, NULL, save_depth, argc, argv); + if (cmd_info.match_status == _SH_CMD_STATUS_NeedsSubcommand) { - sh_hdl->select_reg_struct = cmd_info.reg_struct; - sh_hdl->select_cmd = cmd_info.match_cmd; - - /* 把 cmd_param 中分散的参数以空格分隔,合并成一字符串并保存 */ - char *p = sh_hdl->cmd_line; - for (int i = 0; i < argc; i++) + sh_cmd_info_t tmp; + do { - for (int n = 0; argv[i][n] != '\0'; n++) + tmp = _find_registered_command(sh_hdl, NULL, save_depth, argc, argv); + if (tmp.match_status == _SH_CMD_STATUS_Success) { - *p++ = argv[i][n]; + sh_echo(sh_hdl, "%s: it could be a command\r\n", argv[argc - 1]); + return -1; } - if (i + 1 < argc) + } while (tmp.match_status != _SH_CMD_STATUS_NotFound); + + const char *cmd_line = sh_hdl->cmd_buf; + for (int i = 0; i < _SH_ARRAY_COUNT(sh_hdl->cmd_line); i++) + { + if (*cmd_line == '\0' || *cmd_line == ' ') { - *p++ = ' '; - } - else - { - *p++ = '\0'; + break; } + cmd_line++; } - sh_hdl->cmd_buf = p; + + sh_hdl->cmd_buf = _parse_input(&sh_hdl->select_argc, argv, sh_hdl->cmd_line, cmd_line); return 0; } else { - sh_echo(sh_hdl, "%s: not a parent command\r\n", argv[0]); + sh_echo(sh_hdl, "%s: not a parent command\r\n", argv[argc - 1]); } } else { - if (sh_hdl->select_cmd) + if (sh_hdl->select_argc) { - sh_hdl->select_cmd = NULL; + sh_hdl->select_argc = 0; sh_hdl->cmd_buf = sh_hdl->cmd_line; return 0; } @@ -2843,7 +3233,18 @@ SH_CMD_FN(_cmd_select) SH_CMD_CP_FN(_cmd_select_param) { - _sh_completion_cmd(sh_hdl, argc, argv, 1); + uint16_t select_argc = sh_hdl->select_argc; + sh_hdl->select_argc = 0; + _sh_completion_cmd(sh_hdl, argc, argv, 1, NULL, NULL); + sh_hdl->select_argc = select_argc; +} + +SH_CMD_CP_FN(_cmd_tree_param) +{ + uint16_t select_argc = sh_hdl->select_argc; + sh_hdl->select_argc = 0; + _sh_completion_cmd(sh_hdl, argc, argv, 0, NULL, NULL); + sh_hdl->select_argc = select_argc; } SH_CMD_CP_FN(_cmd_history_param) @@ -2860,9 +3261,9 @@ SH_CMD_FN(_cmd_version) SH_CMD_FN(_cmd_exit) { - if (sh_hdl->select_cmd) + if (sh_hdl->select_argc) { - sh_hdl->select_cmd = NULL; + sh_hdl->select_argc = 0; sh_hdl->cmd_buf = sh_hdl->cmd_line; return 0; } @@ -2879,73 +3280,100 @@ SH_CMD_FN(_cmd_exit) } } -SH_CMD_FN(_cmd_print_help) +SH_CMD_FN(_cmd_print_which) { - sh_cmd_info_t info = sh_cmd_test(sh_hdl, argc, argv); + if (sh_hdl->disable_echo) + { + return 0; + } + + __cp_param_t cp_param = { + .argc = argc, + .argv = argv, + .save_depth = {0}, + }; if (argc == 0) { sh_cp_info_t cp_info = _completion_init(""); + sh_echo(sh_hdl, "Command list >>>"); - cp_info.match_num = 1; - _do_completion_cmd(sh_hdl, &cp_info, NULL, false, 0); - _do_completion_cmd(sh_hdl, &cp_info, NULL, true, 0); + _do_completion_cmd(sh_hdl, &cp_info, &cp_param, false, 0, NULL, NULL); + _do_completion_cmd(sh_hdl, &cp_info, &cp_param, true, 0, NULL, NULL); return 0; } - if (info.err_status == _SH_CMD_STATUS_Bad) + for (int i = 0; true; i++) { - sh_echo(sh_hdl, "sh: help: not found Command '"); - for (int i = 0; i < argc; i++) + sh_cmd_info_t cp_info = _find_registered_command(sh_hdl, NULL, cp_param.save_depth, argc, argv); + if (cp_info.match_status == _SH_CMD_STATUS_NotFound) { - sh_echo(sh_hdl, "%s%s", argv[i], i + 1 < argc ? " " : "'\r\n"); - } - return 0; - } - - int total_len = 0; - for (int i = 0; i < info.match_count; i++) - { - total_len += strlen(argv[i]) + 1; - } - total_len += 3; - for (int i = 1; i <= info.match_count; i++) - { - sh_cmd_info_t tmp = sh_cmd_test(sh_hdl, i, argv); - - if (i == 1) - { - sh_echo(sh_hdl, "build in %s:%d\r\n", _FILENAME(info.reg_struct->file), info.reg_struct->line); - } - - int print_len = 0; - for (int j = 0; j < i; j++) - { - print_len += sh_echo(sh_hdl, "%s%s", argv[j], j + 1 < i ? " " : ""); - } - for (int j = 0; j < total_len - print_len; j++) - { - sh_echo(sh_hdl, " "); - } - sh_echo(sh_hdl, "-- %s\r\n", tmp.match_cmd->help); - - if (i == info.match_count) - { - if (info.err_status == _SH_CMD_STATUS_Success && info.match_cmd->sub_fn.cp_fn == NULL) + if (i == 0) { - break; + sh_echo(sh_hdl, "sh: which: not found Command '"); + for (int i = 0; i < argc; i++) + { + sh_echo(sh_hdl, "%s%s", argv[i], i + 1 < argc ? " " : "'\r\n"); + } } + return 0; + } + + if (cp_info.cmd_count < argc) + { + continue; + } + + if (i > 0) + { + sh_echo(sh_hdl, "\r\n"); + } + + int total_len = 0; + for (int i = 0; i < cp_info.cmd_count; i++) + { + total_len += strlen(argv[i]) + 1; + } + total_len += 3; + uint16_t save_depth[_MAX_SAVE_DEPTH] = {0}; + for (int i = 1; i <= cp_info.cmd_count; i++) + { + sh_cmd_info_t tmp = _find_registered_command(sh_hdl, NULL, save_depth, i, argv); + + if (i == 1) + { + sh_echo(sh_hdl, "build in %s:%d\r\n", _FILENAME(cp_info.reg_struct->file), cp_info.reg_struct->line); + } + + int print_len = 0; for (int j = 0; j < i; j++) { - sh_echo(sh_hdl, "%s%s", argv[j], j + 1 < i ? " " : ""); + print_len += sh_echo(sh_hdl, "%s%s", argv[j], j + 1 < i ? " " : ""); } - if (info.err_status == _SH_CMD_STATUS_Incomplete) + for (int j = 0; j < total_len - print_len; j++) { - sh_echo(sh_hdl, " <...>\r\n"); // 命令未完整 + sh_echo(sh_hdl, " "); } - else if (info.match_cmd->sub_fn.cp_fn) + sh_echo(sh_hdl, "-- %s\r\n", tmp.match_cmd->help); + + if (i == cp_info.cmd_count) { - sh_echo(sh_hdl, " [...]\r\n"); // 带参数自动补全的命令 + if (cp_info.match_status == _SH_CMD_STATUS_Success && cp_info.match_cmd->fn_type == _SH_SUB_TYPE_NUL) + { + break; + } + for (int j = 0; j < i; j++) + { + sh_echo(sh_hdl, "%s%s", argv[j], j + 1 < i ? " " : ""); + } + if (cp_info.match_status == _SH_CMD_STATUS_NeedsSubcommand) + { + sh_echo(sh_hdl, " <...>\r\n"); // 命令未完整 + } + else if (cp_info.match_cmd->fn_type == _SH_SUB_TYPE_CPFN) + { + sh_echo(sh_hdl, " [...]\r\n"); // 带参数自动补全的命令 + } } } } @@ -2953,11 +3381,14 @@ SH_CMD_FN(_cmd_print_help) return 0; } -SH_CMD_CP_FN(_cmd_print_help_param) +SH_CMD_CP_FN(_cmd_print_which_param) { - if (argc + flag != 2 || (argc >= 1 && (strcmp(argv[0], "help") != 0))) + if (argc + flag != 2 || (argc >= 1 && (strcmp(argv[0], "which") != 0))) { - _sh_completion_cmd(sh_hdl, argc, argv, 0); + uint16_t select_argc = sh_hdl->select_argc; + sh_hdl->select_argc = 0; + _sh_completion_cmd(sh_hdl, argc, argv, 0, NULL, NULL); + sh_hdl->select_argc = select_argc; } } @@ -3017,6 +3448,7 @@ SH_DEF_SUB_CMD( SH_DEF_SUB_CMD( _cmd_sh_sublist, SH_SETUP_CMD("echo", "Turn on/off echo through sh_echo()", NULL, _cmd_echo_sublist), // + SH_SETUP_CMD("tree", "Print the command tree", _cmd_tree, _cmd_tree_param), // SH_SETUP_CMD("history", "Show history control", _cmd_history, _cmd_history_param), // #if defined(MIX_SHELL) SH_SETUP_CMD("list-init", "List all auto initialize function\r\n\t* Usage: list-init [filter]", _cmd_print_init_fn, NULL), // @@ -3028,10 +3460,10 @@ SH_DEF_SUB_CMD( SH_REGISTER_CMD( register_internal_command, - SH_SETUP_CMD("sh", "Internal command", NULL, _cmd_sh_sublist), // - SH_SETUP_CMD("help", "Print the complete root command", _cmd_print_help, _cmd_print_help_param), // - SH_SETUP_CMD("select", "Select parent command", _cmd_select, _cmd_select_param), // - SH_SETUP_CMD("exit", "Exit parent command or disconnect", _cmd_exit, NULL), // + SH_SETUP_CMD("sh", "Internal command", NULL, _cmd_sh_sublist), // + SH_SETUP_CMD("which", "Print the complete root command", _cmd_print_which, _cmd_print_which_param), // + SH_SETUP_CMD("select", "Select parent command", _cmd_select, _cmd_select_param), // + SH_SETUP_CMD("exit", "Exit parent command or disconnect", _cmd_exit, NULL), // ); static int _vset_init(void) diff --git a/components/system/source/shell/sh.h b/components/system/source/shell/sh.h index c7c1ecc..69e9515 100755 --- a/components/system/source/shell/sh.h +++ b/components/system/source/shell/sh.h @@ -25,6 +25,16 @@ #define CONFIG_SH_USE_STRTOD 1 /* 允许参数解析工具 sh_parse_value() 解析浮点数 */ #endif +#define _SH_MIN_PARAM 10 + +#ifndef CONFIG_SH_MAX_PARAM +#define CONFIG_SH_MAX_PARAM ((CONFIG_SH_MAX_LINE_LEN / 10) > _SH_MIN_PARAM ? (CONFIG_SH_MAX_LINE_LEN / 10) : _SH_MIN_PARAM) /* 以空格为分隔符的最多命令段数 */ +#endif + +#ifndef CONFIG_SH_MAX_CMD_LEN +#define CONFIG_SH_MAX_CMD_LEN ((CONFIG_SH_MAX_PARAM / 10) > 10 ? (CONFIG_SH_MAX_PARAM / 10) : 10) /* 最大单个命令长度(包含结束符 '\0' ) */ +#endif + typedef struct sh_obj_def sh_t; typedef int (*sh_vprint_fn)(const char *format, va_list va); // 实现发送字符串到终端 @@ -42,17 +52,18 @@ typedef struct // 兼容接口 typedef struct { - const char *arg_str; // 待分析的参数 - int arg_len; // 待解析的参数长度 - char match_str[CONFIG_SH_MAX_LINE_LEN / 4 * 4]; // 保存相同的字符部分 - int list_algin; // 配合 list_fmt ,确定格式中 %-ns 的 n 的值 - int match_num; // 可打印的列表的条目数 - int print_count; // 已打印计算 + const char *arg_str; // 待分析的参数 + int arg_len; // 待解析的参数长度 + char match_str[CONFIG_SH_MAX_CMD_LEN + 1]; // 保存相同的字符部分 + int list_algin; // 配合 list_fmt, 确定格式中 %-ns 的 n 的值 + int match_num; // 可打印的列表的条目数 + int print_count; // 已打印计算 } sh_cp_info_t; typedef enum __packed { SH_CP_OP_NA, // 未发生任何动作 + SH_CP_OP_ERR, // 输入的命令错误而未发生任何动作 SH_CP_OP_CP, // 执行了自动补全 SH_CP_OP_PEND, // 正在列举选项 SH_CP_OP_LIST, // 完成了列举选项 @@ -64,7 +75,7 @@ typedef void (*sh_cp_fn)(sh_t *sh_hdl, int argc, const char *argv[], bool flag); typedef enum { - _SH_SUB_TYPE_VOID, + _SH_SUB_TYPE_NUL, _SH_SUB_TYPE_CPFN, _SH_SUB_TYPE_SUB, } sh_fn_type_t; @@ -91,10 +102,10 @@ typedef struct sh_cmd typedef struct { - sys_psnode_t *node; - const sh_cmd_t *cmd; - const char *file; - int line; + sys_psnode_t *reg_node; // 注意时设置的节点 + const sh_cmd_t *cmd; // 命令数据 + const char *file; // 文件 + int line; // 行号 } sh_cmd_reg_t; typedef struct @@ -105,7 +116,7 @@ typedef struct typedef struct { - sys_psnode_t *node; + sys_psnode_t *reg_node; const sh_key_t *key; } sh_key_reg_t; @@ -113,17 +124,16 @@ typedef struct { enum { - _SH_CMD_STATUS_Bad, // 未找到命令 - _SH_CMD_STATUS_Success, // 成功找到命令函数 - _SH_CMD_STATUS_Incomplete, // 命令不完整 - } err_status; + _SH_CMD_STATUS_NotFound, // 命令不存在 + _SH_CMD_STATUS_NeedsSubcommand, // 命令不完整 + _SH_CMD_STATUS_Success, // 命令找到,可执行 + } match_status; - int match_count; // 表示有多少段为成功匹配的命令段 + unsigned cmd_count; // 命令部分的段落数 - const sh_cmd_reg_t *reg_struct; // 根命令结构的定义地址 - - const sh_cmd_t *match_cmd; // 最后一个匹配的命令信息 + const sh_cmd_reg_t *reg_struct; // 匹配的 sh_cmd_reg_t 对象,用于记录命令 + const sh_cmd_t *match_cmd; // 匹配的 cmd } sh_cmd_info_t; typedef struct // 字符串值解释结果 @@ -166,15 +176,13 @@ typedef struct sh_obj_def // 对象内存结构 sys_pslist_t cmd_list; // 已注册的仅属于对应的终端可见的根命令链 char cmd_line[CONFIG_SH_MAX_LINE_LEN / 4 * 4]; // 当前命令缓存(包括 模块提示、命令行,不包括 提示符) - char *cmd_buf; // 当前命令行在 cmd_line[] 中的指针 + char *cmd_buf; // 当前命令行在 cmd_line[] 中的指针。用于当 select_argc > 0 时,快速定位在 cmd_line[] 中的输入保存位置 uint16_t cmd_stored; // 当前已缓存字符数(不包括 提示符 和 模块提示) uint16_t cmd_input; // 当前光标位置(0.._MAX_CMD_LEN) uint16_t sync_cursor; // 与当前光标同步的位置 int cmd_return; // 指令执行的结果 - const sh_cmd_reg_t *select_reg_struct; // 内部 select 命令 - const sh_cmd_t *select_cmd; // 内部 select 命令 - + int select_argc; // 选中的参数数量。当 select_argc > 0 时,表示当前处于模块模式,且已选中了 select_argc 个参数,字符串保存在 cmd_line[] 中,以'\0' 分隔 char *cmd_history; // 命令的历史记录 int history_size; // cmd_history 的长度 uint16_t *history_index; // 历史记录信息 @@ -200,7 +208,7 @@ typedef struct sh_obj_def // 对象内存结构 #define _SH_DO_CONCAT(X, Y) X##Y #define _SH_CONCAT(X, Y) _SH_DO_CONCAT(X, Y) #define _SH_NAME(NAME) _SH_CONCAT(_SH_CONCAT(_SH_CONCAT(__, NAME), __), __LINE__) -#define _SH_GENERIC_SUB(SUB) (__builtin_types_compatible_p(__typeof(SUB), void *) ? _SH_SUB_TYPE_VOID \ +#define _SH_GENERIC_SUB(SUB) (__builtin_types_compatible_p(__typeof(SUB), void *) ? _SH_SUB_TYPE_NUL \ : __builtin_types_compatible_p(__typeof(SUB), __typeof(_sh_generic_cp_fn)) ? _SH_SUB_TYPE_CPFN \ : __builtin_types_compatible_p(__typeof(SUB), sh_cmd_t const[]) ? _SH_SUB_TYPE_SUB \ : ~0u) @@ -285,7 +293,7 @@ typedef struct sh_obj_def // 对象内存结构 static sh_cmd_t const _SH_NAME(cmd_data_)[] = { \ __VA_ARGS__{0}}; \ static sh_cmd_reg_t const NAME = { \ - .node = &_SH_NAME(cmd_node_), \ + .reg_node = &_SH_NAME(cmd_node_), \ .cmd = _SH_NAME(cmd_data_), \ .file = __FILE__, \ .line = __LINE__, \ diff --git a/components/system/source/shell/sh_vt100.c b/components/system/source/shell/sh_vt100.c index 3c50750..a9e98a1 100755 --- a/components/system/source/shell/sh_vt100.c +++ b/components/system/source/shell/sh_vt100.c @@ -38,7 +38,7 @@ static sh_key_t const _SH_NAME(key_data_)[] = { \ __VA_ARGS__{0}}; \ static sh_key_reg_t const NAME = { \ - .node = &_SH_NAME(key_node_), \ + .reg_node = &_SH_NAME(key_node_), \ .key = _SH_NAME(key_data_), \ }; @@ -427,11 +427,11 @@ int sh_init_vt100(sh_t *sh_hdl, sh_vprint_fn vprint, sh_disconnect_fn disconnect sh_hdl->cmd_buf = sh_hdl->cmd_line; sh_hdl->obj_key_data = _register_vt100_keys; - sh_hdl->obj_key_data.node = &sh_hdl->obj_key_node; + sh_hdl->obj_key_data.reg_node = &sh_hdl->obj_key_node; ret |= sh_register_key(sh_hdl, &sh_hdl->obj_key_data); sh_hdl->obj_cmd_data = _register_cmd_clear; - sh_hdl->obj_cmd_data.node = &sh_hdl->obj_cmd_node; + sh_hdl->obj_cmd_data.reg_node = &sh_hdl->obj_cmd_node; ret |= sh_register_key_cmd(sh_hdl, &sh_hdl->obj_cmd_data); return -!!ret;