Files
ESPC3-wireless/components/system/source/shell/sh.c

3496 lines
105 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file sh.c
* @author LokLiang (lokliang@163.com)
* @brief
* @version 0.1
* @date 2023-03-21
*
* @copyright Copyright (c) 2023
*
*/
#include "sh.h"
#include "sh_vset.h"
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <errno.h>
#define SYS_LOG_DOMAIN "sh"
#include "sys_log.h"
#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'))
#define _IS_DIGIT(c) (c >= '0' && c <= '9')
#define _YEAR ((((__DATE__[7] - '0') * 10 + (__DATE__[8] - '0')) * 10 + (__DATE__[9] - '0')) * 10 + (__DATE__[10] - '0'))
#define _MONTH ((__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n') ? 1 \
: (__DATE__[0] == 'F' && __DATE__[1] == 'e' && __DATE__[2] == 'b') ? 2 \
: (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r') ? 3 \
: (__DATE__[0] == 'A' && __DATE__[1] == 'p' && __DATE__[2] == 'r') ? 4 \
: (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y') ? 5 \
: (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n') ? 6 \
: (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l') ? 7 \
: (__DATE__[0] == 'A' && __DATE__[1] == 'u' && __DATE__[2] == 'g') ? 8 \
: (__DATE__[0] == 'S' && __DATE__[1] == 'e' && __DATE__[2] == 'p') ? 9 \
: (__DATE__[0] == 'O' && __DATE__[1] == 'c' && __DATE__[2] == 't') ? 10 \
: (__DATE__[0] == 'N' && __DATE__[1] == 'o' && __DATE__[2] == 'v') ? 11 \
: 12)
#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 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 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 *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);
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); // 同步热键功能:更新光标位置记录
#if defined(CONFIG_SH_USE_STRTOD) && (CONFIG_SH_USE_STRTOD == 1)
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() 注册的完全匹配的节点
*
* @param code 热键编码,@ref @c sh_vt100.h
* @return const sh_key_t*
*/
static const sh_key_t *_find_key(sh_t *sh_hdl, const char *code)
{
const sh_key_reg_t *test_reg_key;
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++)
{
if (strcmp(test_key[i].code, code) == 0)
{
return &test_key[i];
}
if (strncmp(test_key[i].code, code, sh_hdl->key_stored) == 0)
{
cmd_count++;
}
}
}
if (cmd_count == 0)
{
sh_hdl->key_stored = 0;
sh_hdl->key_str[sh_hdl->key_stored] = '\0';
}
return NULL;
}
/**
* @brief 执行注册根命令
*
* @param list 指定插入的链 @ref @c sh_register_cmd(),sh_register_cmd_hide(),sh_register_key_cmd()
* @param sh_reg 根命令数据
* @return int 0 -- 成功; ! 0 -- 命令中包含空格或命令已存在
*/
static int _register_cmd(sh_t *sh_hdl, sys_pslist_t *list, const sh_cmd_reg_t *sh_reg)
{
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 = &sh_cmd[i];
char err_cmd[_SH_ARRAY_COUNT(sh_hdl->cmd_line) + 1];
err_cmd[0] = '\0';
if (_register_cmd_check_fn(sh_hdl, sh_reg, err_cmd, test_cmd))
{
return -1;
}
}
sys_pslist_append(list, &sh_reg->reg_node);
return 0;
}
/**
* @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(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)
{
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, ' '))
{
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];
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(sh_hdl, sh_reg, err_cmd, test_cmd->sub_fn.sub_cmd) != 0) // 递归查检子命令
{
return -1;
}
}
else
{
if (test_cmd->fn_type == _SH_SUB_TYPE_CPFN)
{
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_SUB)
{
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)
{
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';
}
return 0;
}
/**
* @brief 保存一个字符到热键缓存,并尝试查找对应的热键功能函数。如果热代码错误缓存会被自动清除。
*
* @param c 字符流
*/
static void _find_and_run_key(sh_t *sh_hdl, char c)
{
if (sh_hdl->key_stored < sizeof(sh_hdl->key_str) - 1)
{
sh_hdl->key_str[sh_hdl->key_stored++] = c;
sh_hdl->key_str[sh_hdl->key_stored] = '\0';
const sh_key_t *match_key = _find_key(sh_hdl, sh_hdl->key_str);
if (match_key)
{
sh_hdl->key_stored = 0;
sh_hdl->key_str[sh_hdl->key_stored] = '\0';
match_key->key_func(sh_hdl);
return;
}
}
if (sh_hdl->key_stored >= sizeof(sh_hdl->key_str))
{
sh_hdl->key_stored = 0;
sh_hdl->key_str[sh_hdl->key_stored] = '\0';
}
}
/**
* @brief 在当前的光标(指针)前插入并记录一个字符串。这个动作将同步到终端的命令行显示
*
* @param str 字符串流
*/
static void _insert_str(sh_t *sh_hdl, const char *str)
{
int len = strlen(str);
if (len)
{
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;
}
_apply_history(sh_hdl);
if (sh_hdl->cmd_stored < remain)
{
if (sh_hdl->port.insert_str) /* 设置终端插入一个字符 */
{
sh_hdl->port.insert_str(sh_hdl, 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;
sh_hdl->sync_cursor += len;
sh_hdl->cmd_buf[sh_hdl->cmd_stored] = '\0';
/* 启用刷新命令行模式 */
if (sh_hdl->port.clear_line)
{
sh_refresh_line(sh_hdl);
}
}
}
}
/**
* @brief 以空格为分隔符整理出参数。
* 从 src 中取得结果,输出到 int *argc, char **argv[], char *dest
*
* @param argc[out] 保存参数个数
* @param argv[out] 保存每个参数的地址指向dest
* @param dest[out] 分离出来的参数字符串的副本,注意参数的使用期间需要保留。
* @param src 已记录的命令行数据
* @return char* dest 的下一个可用地址
*/
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)
{
int len = 0;
/* 以空格为分隔符,分段提取命令到 dest */
while (*src == ' ')
{
++src;
}
if (*src == '"') /* 使用双引号分隔的字符串 */
{
++src;
while (1)
{
dest[len] = src[len];
if (dest[len] == '"')
{
++src;
dest[len] = '\0';
}
if (dest[len] == '\0')
{
break;
}
if (dest[len] == '\\')
{
++src;
switch (src[len])
{
case '\0':
--src;
break;
case 'b':
dest[len] = '\b';
break;
case 'f':
dest[len] = '\f';
break;
case 'n':
dest[len] = '\n';
break;
case 'r':
dest[len] = '\r';
break;
case 't':
dest[len] = '\t';
break;
case '\"':
case '\\':
case '/':
dest[len] = src[len];
break;
default:
dest[len] = src[len];
break;
}
}
++len;
}
}
else
{
char split_char;
if (*src == '\'') /* 使用单引号分隔的字符串 */
{
split_char = '\'';
++src;
}
else /* 使用空格分隔的字符串 */
{
split_char = ' ';
}
while (1)
{
dest[len] = src[len];
if (dest[len] == split_char)
{
++src;
dest[len] = '\0';
}
else if (dest[len] == '#' && split_char == ' ') /* 注释 */
{
++src;
dest[len] = '\0';
end = true;
}
if (dest[len] == '\0')
{
break;
}
++len;
}
}
if (len) // dest 数据有效
{
argv[*argc] = dest;
++(*argc);
src = &src[len];
dest = &dest[len + 1];
ret = dest;
}
else
{
break;
}
}
return ret;
}
/**
* @brief 尝试保存到历史记录中
*
* @param src 被保存的字符串(注意恢复空格)
*/
static void _store_history(sh_t *sh_hdl, const char *src)
{
if (sh_hdl->disable_history == 0)
{
int len = strlen(src) + 1;
int history_valid_num;
if (*src == '\0')
{
return;
}
if (len > sh_hdl->history_size)
{
return;
}
if (strcmp(src, sh_hdl->cmd_history) != 0)
{
/* 更新历史信息 */
for (int i = sh_hdl->history_index_num - 1; i > 0; i--)
sh_hdl->history_index[i] = sh_hdl->history_index[i - 1] + len;
/* 分析并刷新有效记录数 */
for (history_valid_num = 0; history_valid_num < sh_hdl->history_index_num - 1; history_valid_num++)
{
int test_index = sh_hdl->history_index[history_valid_num];
if (test_index > sh_hdl->history_size)
{
--history_valid_num;
break;
}
if (sh_hdl->history_index[history_valid_num + 1] <= test_index)
{
break;
}
}
sh_hdl->history_valid_num = history_valid_num;
/* 使历史数据往后移动 len 字节 */
for (int i = sh_hdl->history_size - 1; i >= len; i--)
sh_hdl->cmd_history[i] = sh_hdl->cmd_history[i - len];
/* 复制新数据 */
strcpy(sh_hdl->cmd_history, src);
}
}
}
/**
* @brief 当在回翻历史记录时,一旦对记录修改则立即将当前记录复制到命令行的缓存中
*
* @param sh_hdl
*/
static void _apply_history(sh_t *sh_hdl)
{
if (sh_hdl->history_show)
{
char *str = &sh_hdl->cmd_history[sh_hdl->history_index[sh_hdl->history_show - 1]];
strcpy(sh_hdl->cmd_buf, str);
sh_hdl->cmd_stored = strlen(str);
sh_hdl->history_show = 0;
}
sh_hdl->tab_press_count = 0;
sh_hdl->cp_resource_flag = 0;
}
/**
* @brief 根据当前显示内容(包含回翻的历史记录),获取最大光标位置(不含提示符和模块提示)
*
* @return unsigned 结尾位置 0..n
*/
static unsigned _get_end_pos(sh_t *sh_hdl)
{
if (sh_hdl->history_show)
return strlen(&sh_hdl->cmd_history[sh_hdl->history_index[sh_hdl->history_show - 1]]);
else
return sh_hdl->cmd_stored;
}
/**
* @brief 保存擦除的数据。
* 保存最后一次因 KEY_CTR_U KEY_CTR_K 或 KEY_CTR_W 操作而被擦除的数据,
* 用于 KEY_CTR_Y 还原的数据。如果长度不足则原来已保存的数据被清空
*
* @param line 当前完整的命令行数据
* @param pos 被保存的位置 0..n
* @param n 保存的长度
*/
static void _store_cmd_bak(sh_t *sh_hdl, const char *line, int pos, int n)
{
if (sh_hdl->cmd_bank && sh_hdl->bank_size)
{
if (n > 0)
{
if (n < sh_hdl->bank_size - 1)
{
for (int i = 0; i < n; i++)
{
sh_hdl->cmd_bank[i] = line[pos + i];
}
sh_hdl->cmd_bank[n] = '\0';
}
else
{
sh_hdl->cmd_bank[0] = '\0';
}
}
}
}
/**
* @brief 获取当前提示符(不含模块提示符)
*
* @param sh_hdl
* @return const char*
*/
static const char *sh_ctrl_get_prompt(sh_t *sh_hdl)
{
if (sh_hdl->prompt)
return sh_hdl->prompt;
else
return ">>> ";
}
/**
* @brief 获取当前已输入(或回显的历史)的命令行数据
*
* @return const char* 指向历史记录或命令行缓存
*/
static const char *sh_ctrl_get_line(sh_t *sh_hdl)
{
if (sh_hdl->history_show)
{
return &sh_hdl->cmd_history[sh_hdl->history_index[sh_hdl->history_show - 1]];
}
else
{
return sh_hdl->cmd_buf;
}
}
/**
* @brief 注册该对象的硬件抽象接口
*
* @param port 硬件接口的结构体(不要求静态类型)
* @return int
*/
int sh_register_port(sh_t *sh_hdl, const sh_port_t *port)
{
sh_hdl->port = *port;
return -!port;
}
/**
* @brief 注册根命令
* @ref @c SH_REGISTER_CMD
*
* @param sh_reg 根命令数据
* @return int 0 -- 成功; ! 0 -- 命令中包含空格或命令已存在
*/
int sh_register_cmd(const sh_cmd_reg_t *sh_reg)
{
return _register_cmd(NULL, &s_cmd_list, sh_reg);
}
/**
* @brief 注册根命令。这些根命令不会在补全和帮助中出现,但可被执行
* @ref @c SH_REGISTER_CMD
*
* @param sh_reg 根命令数据
* @return int 0 -- 成功; ! 0 -- 命令中包含空格或命令已存在
*/
int sh_register_cmd_hide(const sh_cmd_reg_t *sh_reg)
{
return _register_cmd(NULL, &s_cmd_list_hide, sh_reg);
}
/**
* @brief 取消注册一个根命令
*
* @param sh_reg 根命令数据
* @return int 0 -- 成功; ! 0 -- 命令中包含空格或命令已存在
*/
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->reg_node));
ret = (ret != false ? ret : sys_pslist_find_and_remove(&s_cmd_list_hide, &sh_reg->reg_node));
return -!ret;
}
/**
* @brief 取消所有已注册命令
*/
void sh_unregister_all(sh_t *sh_hdl)
{
sys_pslist_init(&s_cmd_list);
sys_pslist_init(&s_cmd_list_hide);
sys_pslist_init(&sh_hdl->cmd_list);
}
/**
* @brief 注册一组热键
* 适用于动态加载/卸载
*
* @param sh_key 热键数据
* @return int 0 -- 成功; ! 0 -- 热键中包含空格或热键已存在
*/
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->reg_node);
return 0;
}
/**
* @brief 注册仅属于对应的终端可见的根命令。
* @ref @c SH_REGISTER_CMD
* 适用于动态加载/卸载
*
* @param sh_key 根命令数据
* @return int 0 -- 成功; ! 0 -- 命令中包含空格或命令已存在
*/
int sh_register_key_cmd(sh_t *sh_hdl, const sh_cmd_reg_t *sh_reg)
{
return _register_cmd(sh_hdl, &sh_hdl->cmd_list, sh_reg);
}
/**
* @brief 取消注册一组热键
* 适用于动态加载/卸载
*
* @param sh_key 热键数据
* @return int 0 -- 成功; ! 0 -- 数据不存在
*/
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->reg_node);
}
/**
* @brief 取消注册仅属于对应的终端可见的根命令
* 适用于动态加载/卸载
*
* @param sh_key 根命令数据
* @return int 0 -- 成功; ! 0 -- 数据不存在
*/
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->reg_node);
}
/**
* @brief 配置用于记录历史的内存。sh_init_vt100() 默认不会配置此项。
* @ref @c sh_vt100.c@_install_shell_vt100()
*
* @param mem 内存地址
* @param size 内存大小(字节)
*/
void sh_config_history_mem(sh_t *sh_hdl, void *mem, int size)
{
if (size > 10)
{
int rem = (sizeof(*sh_hdl->history_index) - (unsigned)mem % sizeof(*sh_hdl->history_index)) % sizeof(*sh_hdl->history_index);
size -= rem;
sh_hdl->history_index = (void *)((unsigned)mem + rem);
int history_index_num = size / 10 + 1;
if (history_index_num > (1 << sizeof(*sh_hdl->history_index) * 8) - 1)
{
history_index_num = (1 << sizeof(*sh_hdl->history_index) * 8) - 1;
}
sh_hdl->history_index_num = history_index_num;
sh_hdl->cmd_history = (char *)&sh_hdl->history_index[sh_hdl->history_index_num];
sh_hdl->history_size = size - sizeof(*sh_hdl->history_index) * sh_hdl->history_index_num;
sh_hdl->history_valid_num = 0;
sh_hdl->history_show = 0;
}
}
/**
* @brief 配置用于保存 sh_ctrl_del_word(), sh_ctrl_del_left(), sh_ctrl_del_right() 删除的内容。sh_init_vt100() 默认不会配置此项。
* @ref @c sh_vt100.c@_install_shell_vt100()
*
* @param mem 内存地址
* @param size 内存大小(字节)
*/
void sh_config_backup_mem(sh_t *sh_hdl, void *mem, int size)
{
sh_hdl->cmd_bank = mem;
sh_hdl->bank_size = size;
}
/**
* @brief 输入字符流到内部缓存中并解析和执行。
* 是整个数据解析流程的起点。
*
* @param sh_hdl 由 sh_init_vt100() 初始化的对象
* @param c 字符流
*/
void sh_putc(sh_t *sh_hdl, char c)
{
SYS_ASSERT(sh_hdl != NULL, "");
if (sh_hdl->exec_flag == 0)
{
if (c < ' ' || c >= 0x7F || sh_hdl->key_stored > 0)
{
_find_and_run_key(sh_hdl, c);
return;
}
else
{
char str[2] = {c, '\0'};
_insert_str(sh_hdl, str);
}
}
}
/**
* @brief 同 sh_putc()
*
* @param str 字符串流
*/
void sh_putstr(sh_t *sh_hdl, const char *str)
{
SYS_ASSERT(sh_hdl != NULL, "");
char buf[_SH_ARRAY_COUNT(sh_hdl->cmd_line)];
const char *from = str;
char *to = buf;
for (int i = 0; i < sizeof(buf);)
{
char c = from[i];
if (c < ' ' || c >= 0x7F || sh_hdl->key_stored > 0 || i >= sizeof(buf) - 1)
{
to[i++] = '\0';
_insert_str(sh_hdl, to);
if (c == '\0')
{
break;
}
else
{
sh_putc(sh_hdl, c);
from = &from[i];
to = &to[i];
i = 0;
}
}
else
{
to[i++] = c;
}
}
}
/**
* @brief 同 sh_putstr() 但不会回显
*
* @param str 字符串流
*/
void sh_putstr_quiet(sh_t *sh_hdl, const char *str)
{
SYS_ASSERT(sh_hdl != NULL, "");
bool disable_echo = sh_hdl->disable_echo;
bool disable_history = sh_hdl->disable_history;
sh_port_t port_bak = sh_hdl->port;
sh_hdl->disable_echo = true;
sh_hdl->disable_history = true;
sh_hdl->port.insert_str = NULL;
sh_hdl->port.set_cursor_hoffset = NULL;
sh_hdl->port.clear_line = NULL;
sh_putstr(sh_hdl, str);
sh_hdl->port = port_bak;
sh_hdl->disable_history = disable_history;
sh_hdl->disable_echo = disable_echo;
}
/**
* @brief 设置提示符。
*
* @param prompt 提示符。要求为静态数据。
*/
void sh_set_prompt(sh_t *sh_hdl, const char *prompt)
{
SYS_ASSERT(sh_hdl != NULL, "");
sh_hdl->prompt = prompt;
}
/**
* @brief 清除命令接收缓存(终端显示不会被更新)
*
*/
void sh_reset_line(sh_t *sh_hdl)
{
SYS_ASSERT(sh_hdl != NULL, "");
sh_hdl->cmd_input = 0;
sh_hdl->sync_cursor = 0;
sh_hdl->cmd_stored = 0;
sh_hdl->cmd_buf[sh_hdl->cmd_stored] = '\0';
sh_hdl->key_stored = 0;
sh_hdl->key_str[sh_hdl->key_stored] = '\0';
sh_hdl->tab_press_count = 0;
sh_hdl->cp_resource_flag = 0;
}
/**
* @brief 回显到终端
*
*/
int sh_echo(sh_t *sh_hdl, const char *fmt, ...)
{
SYS_ASSERT(sh_hdl != NULL, "");
if (sh_hdl != NULL && sh_hdl->disable_echo == 0)
{
if (sh_hdl->port.vprint)
{
va_list arg;
va_start(arg, fmt);
int ret = sh_hdl->port.vprint(fmt, arg);
va_end(arg);
return ret;
}
}
return 0;
}
#if defined(CONFIG_SH_USE_STRTOD) && (CONFIG_SH_USE_STRTOD == 1)
/**
* @brief _strtod() 用,跳过空格
*
* @param q 来源字符串
* @return const char* 字符串首个非空格的地址
*/
static const char *_skipwhite(const char *q)
{
const char *p = q;
while (*p == ' ')
{
++p;
}
return p;
}
/**
* @brief 用于替换 <stdlib.h> 中的 strtod()
*/
static double _strtod(const char *str, char **end)
{
double d = 0.0;
int sign;
int n = 0;
const char *p, *a;
a = p = str;
p = _skipwhite(p);
/* decimal part */
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
}
else if (*p == '+')
{
++p;
}
if (_IS_DIGIT(*p))
{
d = (double)(*p++ - '0');
while (*p && _IS_DIGIT(*p))
{
d = d * 10.0 + (double)(*p - '0');
++p;
++n;
}
a = p;
}
else if (*p != '.')
{
goto done;
}
d *= sign;
/* fraction part */
if (*p == '.')
{
double f = 0.0;
double base = 0.1;
++p;
if (_IS_DIGIT(*p))
{
while (*p && _IS_DIGIT(*p))
{
f += base * (*p - '0');
base /= 10.0;
++p;
++n;
}
}
d += f * sign;
a = p;
}
/* exponential part */
if ((*p == 'E') || (*p == 'e'))
{
int e = 0;
++p;
sign = 1;
if (*p == '-')
{
sign = -1;
++p;
}
else if (*p == '+')
{
++p;
}
if (_IS_DIGIT(*p))
{
while (*p == '0')
{
++p;
}
if (*p == '\0')
{
--p;
}
e = (int)(*p++ - '0');
while (*p && _IS_DIGIT(*p))
{
e = e * 10 + (int)(*p - '0');
++p;
}
e *= sign;
}
else if (!_IS_DIGIT(*(a - 1)))
{
a = str;
goto done;
}
else if (*p == 0)
{
goto done;
}
if (d == 2.2250738585072011 && e == -308)
{
d = 0.0;
a = p;
errno = ERANGE;
goto done;
}
if (d == 2.2250738585072012 && e <= -308)
{
d *= 1.0e-308;
a = p;
goto done;
}
d *= pow(10.0, (double)e);
a = p;
}
else if (p > str && !_IS_DIGIT(*(p - 1)))
{
a = str;
goto done;
}
done:
if (end)
{
*end = (char *)a;
}
return d;
}
#endif /* #if defined(CONFIG_SH_USE_STRTOD) && (CONFIG_SH_USE_STRTOD == 1) */
/**
* @brief 参数工具:解析字符串对表示的值,包括 整数/浮点/字符串
*
* @param str 目标解析字符串
* @return sh_parse_t 输出解析结果
*/
sh_parse_t sh_parse_value(const char *str)
{
#define _ATTR_OCT (1 << 0)
#define _ATTR_DEC (1 << 1)
#define _ATTR_HEX (1 << 2)
#define _ATTR_FLOAT (1 << 3)
unsigned attr = 0;
int dot = 0; // '.' 出现次数
sh_parse_t ret = {
.type = _PARSE_TYPE_STRING,
.value.val_string = str,
.endptr = (char *)str,
};
if (str == NULL || *str == '\0')
{
return ret;
}
ret.endptr = &((char *)str)[strlen(str)];
/* 根据首个字符,初步确定属性值为 attr */
do
{
if (*str == '+' || *str == '-')
{
str = &str[1];
}
if (*str == '0')
{
if (str[1] == 'x' || str[1] == 'X')
{
if ((str[2] >= '0' && str[2] <= '9') || (str[2] >= 'a' && str[2] <= 'f') || (str[2] >= 'A' && str[2] <= 'F'))
{
attr = _ATTR_HEX;
str = &str[3];
}
else
{
return ret;
}
}
else
{
attr = _ATTR_OCT | _ATTR_FLOAT;
}
}
else if (*str == '.')
{
attr = _ATTR_FLOAT;
str = &str[1];
dot++;
if (*str == '\0')
{
return ret;
}
}
else if (*str >= '1' && *str <= '9')
{
attr = _ATTR_DEC | _ATTR_FLOAT;
}
} while (0);
/* 按照 attr 给出的可能性,按具体特性单独进行格式检测,若格式正确则进行数值解析并返回 */
do
{
if (attr & _ATTR_OCT) /* 八进制 */
{
while (*str >= '0' && *str <= '7')
{
str = &str[1];
}
if (*str == '\0')
{
if (*ret.value.val_string == '-')
{
ret.type = _PARSE_TYPE_INTEGER;
ret.value.val_integer = strtol(ret.value.val_string, &ret.endptr, 8);
}
else
{
ret.type = _PARSE_TYPE_UNSIGNED;
ret.value.val_unsigned = strtoul(ret.value.val_string, &ret.endptr, 8);
}
return ret;
}
}
if (attr & _ATTR_DEC) /* 十进制 */
{
while (*str >= '0' && *str <= '9')
{
str = &str[1];
}
if (*str == '\0')
{
if (*ret.value.val_string == '-')
{
ret.type = _PARSE_TYPE_INTEGER;
ret.value.val_integer = strtol(ret.value.val_string, &ret.endptr, 10);
}
else
{
ret.type = _PARSE_TYPE_UNSIGNED;
ret.value.val_unsigned = strtoul(ret.value.val_string, &ret.endptr, 10);
}
return ret;
}
}
if (attr & _ATTR_HEX) /* 十六进制 */
{
while ((*str >= '0' && *str <= '9') || (*str >= 'a' && *str <= 'f') || (*str >= 'A' && *str <= 'F'))
{
str = &str[1];
}
if (*str == '\0')
{
if (*ret.value.val_string == '-')
{
ret.type = _PARSE_TYPE_INTEGER;
ret.value.val_integer = strtol(ret.value.val_string, &ret.endptr, 16);
}
else
{
ret.type = _PARSE_TYPE_UNSIGNED;
ret.value.val_unsigned = strtoul(ret.value.val_string, &ret.endptr, 16);
}
return ret;
}
}
#if defined(CONFIG_SH_USE_STRTOD) && (CONFIG_SH_USE_STRTOD == 1)
if (attr & _ATTR_FLOAT) /* 浮点数 */
{
int e = 0; // 'e' 出现次数
while (dot < 2 && e < 2)
{
while (*str >= '0' && *str <= '9')
{
str = &str[1];
}
if (*str == '\0')
{
if (attr & _ATTR_OCT)
{
if (dot + e == 0)
{
return ret; /* 如 08、09 */
}
}
ret.type = _PARSE_TYPE_FLOAT;
ret.value.val_float = _strtod(ret.value.val_string, &ret.endptr);
return ret;
}
switch (*str)
{
case '.':
{
dot++;
str = &str[1];
break;
}
case 'e':
{
e++;
str = &str[1];
if (*str == '\0')
{
return ret;
}
else if (*str == '+' || *str == '-')
{
str = &str[1];
if (*str == '\0')
{
return ret;
}
}
break;
}
default:
return ret;
}
}
}
#endif /* #if defined(CONFIG_SH_USE_STRTOD) && (CONFIG_SH_USE_STRTOD == 1) */
} while (0);
return ret;
}
/**
* @brief 参数工具:以空格为分隔符,合并多个参数
*
* @param dst 用于保存合并结果的内存地址
* @param len 用于保存合并结果的内存长度
* @param argc 参数数量
* @param argv 参数字符串指针
* @return int 合并后的长度(不包含结束字符)
*/
int sh_merge_param(char *dst, int len, int argc, const char *argv[])
{
int ret = 0;
if (len)
{
char *p = dst;
for (int i = 0; i < argc; i++)
{
size_t n = strlen(argv[i]) + 1;
if (n > len)
n = len;
strncpy(p, argv[i], n);
if (i + 1 < argc)
p[n - 1] = ' ';
else
p[n - 1] = '\0';
p = &p[n];
len -= n;
ret += n;
if (len == 0)
{
break;
}
}
--ret;
}
return ret;
}
/**
* @brief 获取上一次命令的执行结果
*
* @param sh_hdl 由 sh_init_vt100() 初始化的对象
* @retval 1 command not found
* @retval 2 command not complete
* @retval 其他值:由被执行的命令的返回值
*/
int sh_get_cmd_result(sh_t *sh_hdl)
{
SYS_ASSERT(sh_hdl != NULL, "");
return sh_hdl->cmd_return;
}
/**
* @brief 执行刷一次当前命令行显示
* 注意仅在 sh_port_t::clear_line 被设置时有效
*/
void sh_refresh_line(sh_t *sh_hdl)
{
SYS_ASSERT(sh_hdl != NULL, "");
if (sh_hdl->port.clear_line)
{
uint16_t input_pos = sh_hdl->cmd_input;
sh_hdl->port.clear_line(sh_hdl);
sh_ctrl_print_cmd_line(sh_hdl);
sh_ctrl_set_input_pos(sh_hdl, input_pos);
}
}
/**
* @brief 自动补全功能用:初始化补全命令信息。
*
* @param cmd 待解析的实例命令字符串
* @return sh_cp_info_t 输出初始的基本补全信息
*/
static sh_cp_info_t _completion_init(const char *cmd)
{
sh_cp_info_t cp_info = {
.arg_str = cmd, // 待分析的参数
.arg_len = strlen(cmd), // 待解析命令长度
.match_num = 0, // 可打印的列表的条目数
.list_algin = 0, // 确定格式中 %-*s 的 * 的值
};
cp_info.match_str[0] = '\0'; // 保存相同的字符部分
return cp_info;
}
/**
* @brief 自动补全功能用:每被执行一次都被更新一次补全信息到 cp_info 或根据 cp_info 直接打印可选项。
*
* @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 *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)
{
if (cp_info->arg_len > _SH_ARRAY_COUNT(cp_info->match_str) - 1)
{
return; // 这将超出 CONFIG_SH_MAX_CMD_LEN 的限制
}
// 如果只按父命令更新且这是一个最终命令,则跳过
if (flag_parent && sub_cmd->cmd_func)
{
return;
}
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)
{
/* 根据参数 suffix_space 自动为候选命令添加空格为结尾 */
int str_len = strlen(sub_cmd->cmd);
char _cmd[str_len + 2];
strcpy(_cmd, sub_cmd->cmd);
if (suffix_space)
{
if (str_len > 0 && _cmd[str_len - 1] != ' ')
{
_cmd[str_len] = ' ';
_cmd[str_len + 1] = '\0';
}
}
int test_len = strlen(_cmd);
if (cp_info->list_algin < test_len)
cp_info->list_algin = test_len;
if (cp_info->match_num)
{
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
{
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);
}
}
}
}
}
/**
* @brief 自动补全功能用:执行一次自动补全命令或打印备选命令的功能
*
* @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 *cp_info,
__cp_param_t *cp_param,
bool print_match,
int flag_parent,
__cp_update_cb update_cb,
char *prefix)
{
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;
do
{
if (print_match)
{
if (cp_info->match_num == 0)
{
return false;
}
if (match_num == cp_info->match_num || cp_info->print_count != 0)
{
update_type = _CP_UPDATE_TYPE_PRINT;
if (update_cb == NULL)
{
if (sh_hdl->cp_operate != SH_CP_OP_PEND)
{
sh_hdl->cp_operate = SH_CP_OP_PEND;
if (update_cb == NULL)
{
sh_echo(sh_hdl, "\r\n");
}
}
}
}
match_num = cp_info->match_num;
cp_info->print_count = 0;
}
else
{
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)
{
return false;
}
return _do_completion_insert(sh_hdl, cp_info);
}
/**
* @brief 自动补全功能用:执行一次自动补全参数或打印备选参数的功能
*
* @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 *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 (cp_info->match_num == 0)
{
return false;
}
if (sh_hdl->cp_operate != SH_CP_OP_PEND)
{
sh_echo(sh_hdl, "\r\n");
sh_hdl->cp_operate = SH_CP_OP_PEND;
}
cp_info->print_count = 0;
}
else
{
cp_info->match_num = 0;
}
for (int i = 0; sh_param[i].cmd != NULL; i++)
{
sh_cmd_t sub_cmd = {
.cmd = sh_param[i].cmd,
.help = sh_param[i].help,
.cmd_func = (__typeof(sub_cmd.cmd_func))1,
.sub_fn = {.nul = NULL},
.fn_type = _SH_SUB_TYPE_NUL,
};
_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)
{
return false;
}
return _do_completion_insert(sh_hdl, cp_info);
}
/**
* @brief 自动补全功能用:执行补全
*
* @param cp_info[out] 由 _completion_init() 初始化的补全信息指针
* @return true 成功执行了一次补全的动作
* @return false 未执行补全的动作
*/
static bool _do_completion_insert(sh_t *sh_hdl, sh_cp_info_t *cp_info)
{
/* 执行自动补全,返回:显示是否被自动更新 */
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, &cp_info->match_str[cp_info->arg_len]);
sh_hdl->cp_operate = SH_CP_OP_CP;
return true;
}
}
/**
* @brief 在展示备选项后恢复命令行的显示
*/
static void _do_restore_line(sh_t *sh_hdl)
{
if (sh_hdl->cp_operate == SH_CP_OP_PEND)
{
uint16_t cmd_input = sh_hdl->cmd_input;
sh_ctrl_print_cmd_line(sh_hdl);
sh_ctrl_set_input_pos(sh_hdl, cmd_input);
sh_hdl->cp_operate = SH_CP_OP_LIST;
}
}
/**
* @brief 确定每个命令或参数的对齐的长度
*
* @param cp_info[out] 由 _completion_update() 更新的补全信息指针
* @return int 每个命令或参数应占用的打印空间
*/
static int _calc_list_algin(sh_cp_info_t *cp_info)
{
return (cp_info->list_algin > 8 ? cp_info->list_algin : 8) + 2;
}
/**
* @brief 完整解析命令并获取命令信息。注意不管是不是在模块模式下,都需要解析完整的命令
*
* @param argc 命令数量
* @param argv 命令字符串指针
* @return sh_cmd_info_t 命令信息
*/
sh_cmd_info_t sh_cmd_test(sh_t *sh_hdl, int argc, const char *argv[])
{
SYS_ASSERT(sh_hdl != NULL, "");
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 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, __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;
}
/* 初始化 cp_info 和 cmd_info */
cp_info = _completion_init("");
if (sh_hdl->select_argc != 0)
{
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++)
{
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
{
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 - 1]);
}
else
{
cp_info = _completion_init(argv[cmd_info.cmd_count]);
}
cp_param.argc = argc - 1;
}
if (cmd_info.match_status == _SH_CMD_STATUS_Success) // 完整的命令
{
if (cmd_info.match_cmd->fn_type == _SH_SUB_TYPE_CPFN) // 带参数自动补全的命令
{
do
{
if (sh_hdl->cp_resource_flag) // 仅作用于使用了 sh_completion_resource() 的情况
{
if (sh_hdl->tab_press_count)
{
cp_info.match_num = sh_hdl->cp_match_num;
cp_info.list_algin = sh_hdl->cp_list_algin;
if (cp_info.match_num < 2)
{
return sh_hdl->cp_operate;
}
if (sh_hdl->cp_operate != SH_CP_OP_PEND)
{
sh_hdl->cp_operate = SH_CP_OP_PEND;
sh_echo(sh_hdl, "\r\n");
}
}
}
++sh_hdl->exec_flag;
sh_hdl->cp_info = &cp_info;
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;
if (sh_hdl->cp_resource_flag) // 仅作用于使用了 sh_completion_resource() 的情况
{
int cp_resource_flag = sh_hdl->cp_resource_flag;
if (sh_hdl->tab_press_count == 0)
{
if (cp_info.match_num)
{
sh_hdl->tab_press_count = 1;
sh_hdl->cp_match_num = cp_info.match_num;
sh_hdl->cp_list_algin = cp_info.list_algin;
_do_completion_insert(sh_hdl, &cp_info);
}
}
else
{
/* 恢复当前命令行显示 */
_do_restore_line(sh_hdl);
}
sh_hdl->cp_resource_flag = cp_resource_flag;
}
} while (0);
}
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
{
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;
}
}
}
}
return sh_hdl->cp_operate;
}
/**
* @brief 仅在 cp_fn() 中可用,根据当前命令行内容,在给出的参数结构中查找并自动补全参数/打印备选参数的全过程
*
* @param param 允许为 NULL。包含所有备选项的参数的结构。注意结构体必须有值为 NULL 的成员为结束。
* @return sh_cp_op_t 自动补全参数/打印备选参数的执行结果
*/
sh_cp_op_t sh_completion_param(sh_t *sh_hdl, const sh_cp_param_t *param)
{
SYS_ASSERT(sh_hdl != NULL, "");
if (sh_hdl->exec_flag == 0)
{
sh_hdl->cp_operate = SH_CP_OP_NA;
}
do
{
sh_hdl->cp_resource_flag = 0;
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;
}
if (param == 0)
{
break;
}
if (_do_completion_param(sh_hdl, sh_hdl->cp_info, param, false) == false)
{
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_restore_line(sh_hdl);
}
else
{
sh_hdl->tab_press_count = 1;
}
}
}
} while (0);
return sh_hdl->cp_operate;
}
/**
* @brief 仅在 cp_fn() 中可用,根据当前命令行内容,逐个给出可供查找的提示符。为内部提供 自动补全/打印备选项 的具体内容。
* 自动补全/打印备行项 将在 cp_fn() 退出后自动执行。
* @ref @c fatfs_shell.c@_scan_files()/@_cmd_df_cp()
*
* @param arg_str 指定已输入的待自动补全的字符串当为NULL时表示按当前命令行自动填充。
* 使用场景:在自动补全路径时, arg_str 表示仅关注路径的最后一部分,而不是整个路径,以便于在补全时只显示当前目录下的文件。
* @param res_str 候选字符串
* @param help 帮助描述信息。如果值为 NULL 则不会被打印
*/
void sh_completion_resource(sh_t *sh_hdl, const char *arg_str, const char *res_str, const char *help)
{
SYS_ASSERT(sh_hdl != NULL, "");
if (sh_hdl->cp_info == NULL)
{
if (sh_hdl->cp_resource_flag == 0)
{
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;
}
sh_hdl->cp_resource_flag = 1; // 标记执行了本函数
if (res_str == NULL || *res_str == '\0')
{
return;
}
if (arg_str)
{
sh_hdl->cp_info->arg_str = arg_str;
sh_hdl->cp_info->arg_len = strlen(arg_str);
}
sh_cmd_t sub_cmd = {
.cmd = res_str,
.help = help,
.cmd_func = (__typeof(sub_cmd.cmd_func))1,
.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,
false,
sh_hdl->tab_press_count ? _CP_UPDATE_TYPE_PRINT : _CP_UPDATE_TYPE_UPDATE,
NULL,
max_lines,
0,
NULL,
NULL,
0,
NULL);
}
/**
* @brief 获取当前输入的最后一个参数是否完整(以空格分开)
*
* @return true 已完整输入一个参数(结尾有空格)
* @return false 正在输入参数
*/
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] == ' ');
}
/**
* @brief 同步热键功能:根据当前命令行缓存的指针位置,更新光标位置记录
*
*/
static void _update_cursor(sh_t *sh_hdl)
{
sh_hdl->tab_press_count = 0;
sh_hdl->cp_resource_flag = 0;
if (sh_hdl->port.set_cursor_hoffset)
{
if (sh_hdl->cmd_input != sh_hdl->sync_cursor)
{
int offset = strlen(sh_hdl->prompt) + (sh_hdl->cmd_buf - sh_hdl->cmd_line); // 输入命令的起始绝对位置
int last_pos = sh_hdl->sync_cursor + offset;
int new_pos = sh_hdl->cmd_input + offset;
sh_hdl->port.set_cursor_hoffset(sh_hdl, last_pos, new_pos);
}
}
sh_hdl->sync_cursor = sh_hdl->cmd_input;
}
/**
* @brief 配合 sh_completion_resource() 使用,在下次进入 cp_fn() 时获取前一次自动补全的执行效果
*
* @return sh_cp_op_t 最后一次自动补全的执行效果
*/
sh_cp_op_t sh_get_cp_result(sh_t *sh_hdl)
{
SYS_ASSERT(sh_hdl != NULL, "");
return sh_hdl->cp_operate;
}
/**
* @brief 同步热键功能:自动实例/打印命令
*
*/
void sh_ctrl_tab(sh_t *sh_hdl)
{
int argc;
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 */
_parse_input(&argc, pargv, cmd_param, cmd_param);
/* 自动补全命令 */
_sh_completion_cmd(sh_hdl, argc, pargv, 0, NULL, NULL);
}
/**
* @brief 同步热键功能:执行已缓存到的命令
*
* @return true 命令行中的命令被找到并且执行完毕
* @return false 没有执行任何命令
*/
bool sh_ctrl_enter(sh_t *sh_hdl)
{
int argc;
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 */
_parse_input(&argc, pargv, cmd_param, cmd_line);
if (argc)
{
/* 将当前回翻的记录复制到缓存中 */
_apply_history(sh_hdl);
sh_cmd_info_t cmd_info;
if (sh_hdl->select_argc != 0)
{
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", pargv[i], i + 1 < argc ? " " : "");
}
if (cmd_info.match_status == _SH_CMD_STATUS_NeedsSubcommand)
{
sh_echo(sh_hdl, ": command not complete\r\n");
sh_hdl->cmd_return = 2;
}
else
{
sh_echo(sh_hdl, ": command not found\r\n");
sh_hdl->cmd_return = 1;
}
ret = false;
}
/* 去除重复的空格,但保持引号内的空格 */
int index_src = 0;
int index_dst = 0;
char split_char = ' ';
while (cmd_line[index_src] != '\0' && cmd_line[index_src] == ' ')
{
index_src++;
}
while (cmd_line[index_src] != '\0')
{
char c = cmd_line[index_src++];
if (split_char == ' ')
{
if (c == '\'' || c == '"')
{
split_char = c;
}
}
else if (c == split_char)
{
split_char = ' ';
}
if (split_char == ' ' && c == ' ')
{
while (cmd_line[index_src] != '\0' && cmd_line[index_src] == ' ')
{
index_src++;
}
if (cmd_line[index_src] == '\0')
{
break;
}
}
cmd_param[index_dst++] = c;
}
cmd_param[index_dst] = '\0';
/* 尝试保存到历史记录中 */
_store_history(sh_hdl, cmd_param);
}
else
{
sh_hdl->tab_press_count = 0;
sh_hdl->cp_resource_flag = 0;
}
sh_reset_line(sh_hdl);
--sh_hdl->exec_flag;
return ret;
}
/**
* @brief 同步热键功能:删除光标的后 n 个字符
*
* @param n 字符数
* @return true 执行了动作
* @return false 未执行动作
*/
bool sh_ctrl_delete(sh_t *sh_hdl, int n)
{
if (n == 0)
{
return true;
}
_apply_history(sh_hdl);
if (sh_hdl->cmd_input + n <= sh_hdl->cmd_stored)
{
for (int i = sh_hdl->cmd_input; i + n < sh_hdl->cmd_stored; i++) /* 使光标之后的数据往前移动一个字节 */
sh_hdl->cmd_buf[i] = sh_hdl->cmd_buf[i + n];
sh_hdl->cmd_stored -= n;
sh_hdl->cmd_buf[sh_hdl->cmd_stored] = '\0';
return true;
}
else
{
return false;
}
}
/**
* @brief 同步热键功能:删除光标的前 n 个字符
*
* @param n 字符数
* @return true 执行了动作
* @return false 未执行动作
*/
bool sh_ctrl_backspace(sh_t *sh_hdl, int n)
{
if (n == 0)
{
return true;
}
_apply_history(sh_hdl);
if (sh_hdl->cmd_input >= n)
{
sh_hdl->cmd_input -= n;
sh_hdl->sync_cursor -= n;
return sh_ctrl_delete(sh_hdl, n);
}
else
{
return false;
}
}
/**
* @brief 同步热键功能:查看上一个命令,并更新光标位置记录
*
* @return true 执行了动作
* @return false 未执行动作
*/
bool sh_ctrl_up(sh_t *sh_hdl)
{
if (sh_hdl->history_show < sh_hdl->history_valid_num)
{
++sh_hdl->history_show;
sh_ctrl_end(sh_hdl);
return true;
}
else
{
return false;
}
}
/**
* @brief 同步热键功能:查看下一个命令,并更新光标位置记录
*
* @return true 执行了动作
* @return false 未执行动作
*/
bool sh_ctrl_down(sh_t *sh_hdl)
{
if (sh_hdl->history_show > 0)
{
--sh_hdl->history_show;
sh_ctrl_end(sh_hdl);
return true;
}
else
{
return false;
}
}
/**
* @brief 同步热键功能:光标左移,并更新光标位置记录
*
*/
void sh_ctrl_left(sh_t *sh_hdl)
{
if (sh_hdl->cmd_input > 0)
--sh_hdl->cmd_input;
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能:光标右移,并更新光标位置记录
*
*/
void sh_ctrl_right(sh_t *sh_hdl)
{
if (sh_hdl->cmd_input < _get_end_pos(sh_hdl))
++sh_hdl->cmd_input;
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能:光标移到开头位置,并更新光标位置记录
*
*/
void sh_ctrl_home(sh_t *sh_hdl)
{
sh_hdl->cmd_input = 0;
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能:光标移到结尾位置,并更新光标位置记录
*
*/
void sh_ctrl_end(sh_t *sh_hdl)
{
sh_hdl->cmd_input = _get_end_pos(sh_hdl);
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能光标移到指定位置不包括提示符起始位置为0并更新光标位置记录
*
* @param pos 0.._get_end_pos(sh_hdl)
*/
void sh_ctrl_set_cursor(sh_t *sh_hdl, int pos)
{
if (pos < 0)
{
sh_hdl->cmd_input = 0;
}
else if (pos > _get_end_pos(sh_hdl))
{
sh_hdl->cmd_input = _get_end_pos(sh_hdl);
}
else
{
sh_hdl->cmd_input = pos;
}
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能:光标移到当前单词的开头,并更新光标位置记录
*
*/
void sh_ctrl_word_head(sh_t *sh_hdl)
{
const char *line = sh_ctrl_get_line(sh_hdl);
int pos = sh_hdl->cmd_input;
while (pos)
{
if (_IS_ALPHA(line[pos - 1]) || _IS_DIGIT(line[pos - 1]))
{
break;
}
--pos;
}
while (pos)
{
if (!_IS_ALPHA(line[pos - 1]) && !_IS_DIGIT(line[pos - 1]))
{
break;
}
--pos;
}
sh_ctrl_set_cursor(sh_hdl, pos);
}
/**
* @brief 同步热键功能:光标移到当前单词的结尾,并更新光标位置记录
*
*/
void sh_ctrl_word_tail(sh_t *sh_hdl)
{
const char *line = sh_ctrl_get_line(sh_hdl);
int pos = sh_hdl->cmd_input;
while (line[pos] != '\0')
{
if (_IS_ALPHA(line[pos]) || _IS_DIGIT(line[pos]))
{
break;
}
++pos;
}
while (line[pos] != '\0')
{
if (!_IS_ALPHA(line[pos]) && !_IS_DIGIT(line[pos]))
{
break;
}
++pos;
}
sh_ctrl_set_cursor(sh_hdl, pos);
}
/**
* @brief 同步热键功能:打印一次命令行内容
*
*/
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_argc)
{
sh_echo(sh_hdl, _COLOR_P);
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);
sh_echo(sh_hdl, line);
sh_hdl->cmd_input = strlen(line);
sh_hdl->sync_cursor = sh_hdl->cmd_input;
_update_cursor(sh_hdl);
}
/**
* @brief 同步热键功能:擦除光标前的单词内容
*
* @return unsigned 成功擦除的字符数
*/
unsigned sh_ctrl_del_word(sh_t *sh_hdl)
{
const char *line = sh_ctrl_get_line(sh_hdl);
int pos = sh_hdl->cmd_input;
int n = 0;
while (pos)
{
if (_IS_ALPHA(line[pos - 1]) || _IS_DIGIT(line[pos - 1]))
{
break;
}
--pos;
++n;
}
while (pos)
{
if (!_IS_ALPHA(line[pos - 1]) && !_IS_DIGIT(line[pos - 1]))
{
break;
}
--pos;
++n;
}
_store_cmd_bak(sh_hdl, line, pos, n);
sh_ctrl_backspace(sh_hdl, n);
return n;
}
/**
* @brief 同步热键功能:擦除光标前到行首的全部内容
*
* @return unsigned 成功擦除的字符数
*/
unsigned sh_ctrl_del_left(sh_t *sh_hdl)
{
const char *line = sh_ctrl_get_line(sh_hdl);
int pos = sh_hdl->cmd_input;
_store_cmd_bak(sh_hdl, line, 0, pos);
sh_ctrl_backspace(sh_hdl, pos);
return pos;
}
/**
* @brief 同步热键功能:擦除光标后到行尾的全部内容
*
* @return unsigned 成功擦除的字符数
*/
unsigned sh_ctrl_del_right(sh_t *sh_hdl)
{
const char *line = sh_ctrl_get_line(sh_hdl);
int pos = sh_hdl->cmd_input;
int n = strlen(line) - pos;
_store_cmd_bak(sh_hdl, line, pos, n);
sh_ctrl_delete(sh_hdl, n);
return n;
}
/**
* @brief 同步热键功能:插入 sh_ctrl_del_word(), sh_ctrl_del_left(), sh_ctrl_del_right() 擦除的内容
*
* @return true 执行了动作
* @return false 未执行动作
*/
bool sh_ctrl_undelete(sh_t *sh_hdl)
{
if (sh_hdl->cmd_bank && sh_hdl->bank_size)
{
if (sh_hdl->cmd_bank[0])
{
_insert_str(sh_hdl, sh_hdl->cmd_bank);
return true;
}
}
return false;
}
/**
* @brief 同步热键功能:查询当前是否使用了 select 命令进入的模块模式(快捷命令模式)
*
* @return true 当前在模块模式
* @return false 当前未在模块模式
*/
bool sh_ctrl_is_module(sh_t *sh_hdl)
{
return (bool)(!!sh_hdl->select_argc);
}
/**
* @brief 设置当前指针(光标)位置(不包括提示符和模块提示)
*
* @param input_pos 0.._get_end_pos(sh_hdl)
*/
void sh_ctrl_set_input_pos(sh_t *sh_hdl, uint16_t input_pos)
{
if (input_pos > _get_end_pos(sh_hdl))
input_pos = _get_end_pos(sh_hdl);
sh_hdl->cmd_input = input_pos;
_update_cursor(sh_hdl);
}
/**
* @brief 获取当前指针光标位置不包括提示符和模块提示起始位置为0
*
* @param input_pos 0.._get_end_pos(sh_hdl)
*/
uint16_t sh_ctrl_get_input_pos(sh_t *sh_hdl)
{
return sh_hdl->cmd_input;
}
/**
* @brief 根据当前显示内容(包含回翻的历史记录),获取当前光标位置(提示符+模块提示+命令)
*
* @return uint16_t 获取当前光标位置(提示符+模块提示+命令)
*/
uint16_t sh_ctrl_get_line_pos(sh_t *sh_hdl)
{
return strlen(sh_hdl->prompt) +
(sh_hdl->cmd_buf - sh_hdl->cmd_line) +
sh_hdl->cmd_input;
}
/**
* @brief 根据当前显示内容(包含回翻的历史记录),获取最大光标位置(包含提示符和模块提示)
* 用于模拟清除当前行
*
* @return uint16_t 当前命令行显示的总字符数
*/
uint16_t sh_ctrl_get_line_len(sh_t *sh_hdl)
{
return strlen(sh_hdl->prompt) +
(sh_hdl->cmd_buf - sh_hdl->cmd_line) +
_get_end_pos(sh_hdl);
}
/**
* @brief 获取命令行部分的字符(提示符+模块提示+命令)
*
* @param pos 命令行位置 0.._get_end_pos(sh_hdl)
* @return char 字符
*/
char sh_ctrl_get_line_char(sh_t *sh_hdl, uint16_t pos)
{
int test_len;
test_len = strlen(sh_hdl->prompt);
if (pos < test_len)
{
return sh_hdl->prompt[pos];
}
pos -= test_len;
test_len = sh_hdl->cmd_buf - sh_hdl->cmd_line;
if (pos < test_len)
{
return sh_hdl->cmd_line[pos];
}
pos -= test_len;
test_len = _get_end_pos(sh_hdl);
if (pos < test_len)
{
if (sh_hdl->history_show)
return sh_hdl->cmd_history[sh_hdl->history_index[sh_hdl->history_show - 1] + pos];
else
return sh_hdl->cmd_buf[pos];
}
return '\0';
}
/* 内部默认命令 -------------------------------------------------------------------------------------------- */
#if defined(MIX_SHELL)
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 cmd_count = 0;
sh_echo(sh_hdl, "Automatic initialization list >>>\r\n");
while (start < end)
{
if (start->level + start->prior != 0 &&
start->level * 100 + start->prior != 100 &&
start->level * 100 + start->prior != 999)
{
const char *lev2char[] = {
"BOARD",
"PREV",
"DEVICE",
"COMPONENT",
"ENV",
"APP",
};
char buf[0x200];
SYS_SNPRINT(buf, sizeof(buf), "%-10s %d-%-2d %s:%d @%s",
start->level < sizeof(lev2char) / sizeof(lev2char[0]) ? lev2char[start->level] : "MISC",
start->level,
start->prior,
_FILENAME(start->file),
start->line,
start->fn_name);
int result = !argc;
for (int i = 0; i < argc; i++)
{
if (strstr(buf, argv[i]))
{
result = 1;
break;
}
}
if (result)
{
sh_echo(sh_hdl, "%s\r\n", buf);
cmd_count++;
}
}
start = &start[1];
}
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 cmd_count = 0;
sh_echo(sh_hdl, "Internal module cmd list >>>\r\n");
const struct
{
module_type_t type;
const char *name;
} type2char[] = {
{MODULE_TYPE_DEVICE, "DEVICE"},
};
for (int n = 0; n < sizeof(type2char) / sizeof(type2char[0]); n++)
{
while (start < end)
{
if (start->type == type2char[n].type)
{
char buf[0x200];
SYS_SNPRINT(buf, sizeof(buf), "%p %-10s %-10s %s:%d",
start->obj,
type2char[n].name,
start->name,
_FILENAME(start->file),
start->line);
int result = !argc;
for (int i = 0; i < argc; i++)
{
if (strstr(buf, argv[i]))
{
result = 1;
break;
}
}
if (result)
{
sh_echo(sh_hdl, "%s\r\n", buf);
cmd_count++;
}
}
start = &start[1];
}
}
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--;)
{
sh_echo(sh_hdl, "%s\r\n", &sh_hdl->cmd_history[sh_hdl->history_index[i]]);
}
}
else
{
if (strcmp(argv[0], "enable") == 0)
{
sh_hdl->disable_history = false;
}
else if (strcmp(argv[0], "disable") == 0)
{
sh_hdl->disable_history = true;
}
else
{
sh_echo(sh_hdl, "%s: parameter error\r\n", argv[0]);
}
}
return 0;
}
SH_CMD_FN(_cmd_select)
{
uint16_t save_depth[_MAX_SAVE_DEPTH] = {0};
if (argc)
{
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_cmd_info_t tmp;
do
{
tmp = _find_registered_command(sh_hdl, NULL, save_depth, argc, argv);
if (tmp.match_status == _SH_CMD_STATUS_Success)
{
sh_echo(sh_hdl, "%s: it could be a command\r\n", argv[argc - 1]);
return -1;
}
} 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 == ' ')
{
break;
}
cmd_line++;
}
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[argc - 1]);
}
}
else
{
if (sh_hdl->select_argc)
{
sh_hdl->select_argc = 0;
sh_hdl->cmd_buf = sh_hdl->cmd_line;
return 0;
}
}
return -1;
}
SH_CMD_CP_FN(_cmd_select_param)
{
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)
{
sh_completion_resource(sh_hdl, NULL, "enable", NULL);
sh_completion_resource(sh_hdl, NULL, "disable", NULL);
}
SH_CMD_FN(_cmd_version)
{
sh_echo(sh_hdl, "sh v" SH_VERSION " build %04d-%02d-%02d\r\n", _YEAR, _MONTH, _DAY);
return 0;
}
SH_CMD_FN(_cmd_exit)
{
if (sh_hdl->select_argc)
{
sh_hdl->select_argc = 0;
sh_hdl->cmd_buf = sh_hdl->cmd_line;
return 0;
}
else if (sh_hdl->port.disconnect)
{
sh_hdl->port.disconnect();
return 0;
}
else
{
extern void drv_hal_sys_exit(void);
drv_hal_sys_exit();
return -1;
}
}
SH_CMD_FN(_cmd_print_which)
{
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 >>>");
_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;
}
for (int i = 0; true; 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)
{
if (i == 0)
{
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++)
{
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 == cp_info.cmd_count)
{
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"); // 带参数自动补全的命令
}
}
}
}
return 0;
}
SH_CMD_CP_FN(_cmd_print_which_param)
{
if (argc + flag != 2 || (argc >= 1 && (strcmp(argv[0], "which") != 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;
}
}
SH_CMD_FN(_cmd_echo_on)
{
sh_hdl->disable_echo = 0;
return 0;
}
SH_CMD_FN(_cmd_echo_off)
{
sh_hdl->disable_echo = 1;
return 0;
}
SH_CMD_FN(_cmd_parse_test)
{
for (int i = 0; i < argc; i++)
{
sh_parse_t pv = sh_parse_value(argv[i]);
switch (pv.type)
{
case _PARSE_TYPE_STRING: // 字符串
sh_echo(sh_hdl, "param%d: type = string \"%s\"\r\n",
i + 1,
pv.value.val_string);
break;
case _PARSE_TYPE_INTEGER: // 带符号整型
sh_echo(sh_hdl, "param%d: type = inteter, value = %u(dec) 0x%x(hex) 0%o(oct)\r\n",
i + 1,
pv.value.val_unsigned, pv.value.val_unsigned, pv.value.val_unsigned);
break;
case _PARSE_TYPE_UNSIGNED: // 无符号整型
sh_echo(sh_hdl, "param%d: type = inteter, value = %d(dec) 0x%x(hex) 0%o(oct)\r\n",
i + 1,
pv.value.val_integer, pv.value.val_integer, pv.value.val_integer);
break;
case _PARSE_TYPE_FLOAT: // 浮点
sh_echo(sh_hdl, "param%d: type = float, value = %f\r\n",
i + 1,
pv.value.val_float);
break;
default:
break;
}
}
return 0;
}
SH_DEF_SUB_CMD(
_cmd_echo_sublist,
SH_SETUP_CMD("on", "Enable to feedback the command line", _cmd_echo_on, NULL), //
SH_SETUP_CMD("off", "Disable to feedback the command line", _cmd_echo_off, NULL), //
);
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), //
SH_SETUP_CMD("list-module", "List all module structure\r\n\t* Usage: list-module [filter]", _cmd_print_module_struct, NULL), //
#endif
SH_SETUP_CMD("version", "Print the shell version", _cmd_version, NULL), //
SH_SETUP_CMD("parse-value", "Parse the value of a string demo", _cmd_parse_test, NULL), //
);
SH_REGISTER_CMD(
register_internal_command,
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)
{
vset_init(&g_uart_handle_vt100, NULL);
return 0;
}
INIT_EXPORT_COMPONENT(_vset_init);
/**
* @brief 用于显式初始化内部函数。
* 当自动初始化函数不适用时,使用此函数可强制初始化内部的一些功能。包含:
* 1. 执行 register_internal_command() 注册内部的基本命令
* 2. 执行 sh_init_vt100() 控制相关的初始化
*
* @param out 定义执行 vprintf 的函数函数
*/
void sh_register_external(sh_vprint_fn out)
{
sh_register_cmd(&register_internal_command);
if (out == NULL)
{
out = SYS_VPRINT;
}
if (sh_init_vt100(&g_uart_handle_vt100, out, NULL) == 0)
{
static uint8_t history_mem[255];
static uint8_t bank_mem[CONFIG_SH_MAX_LINE_LEN];
sh_config_history_mem(&g_uart_handle_vt100, history_mem, sizeof(history_mem));
sh_config_backup_mem(&g_uart_handle_vt100, bank_mem, sizeof(bank_mem));
_vset_init();
}
}