扩展机制
XWSH 的扩展机制:CherryRL 库集成细节、回调函数说明和历史记录实现。
Categories:
5 分钟阅读
CherryRL 库集成
XWSH 使用 CherryRL 库提供专业的命令行编辑功能,包括历史记录、自动补全、ANSI 转义序列处理等。集成代码位于 xwmd/cli/xwsh/readline.c。
CherryRL 实例
// readline.c:35-37
chry_readline_t xwsh_cherryrl; // CherryRL 实例
char xwsh_cherryrl_prompt[XWSH_RL_PROMPT_MAXSIZE]; // 提示符缓冲区(64字节)
char xwsh_cherryrl_history[XWSH_RL_HISTORY_MAXSIZE]; // 历史记录缓冲区(1024字节)
缓冲区大小要求:
- 历史记录缓冲区大小必须是 2 的幂(1024 符合要求)
- 提示符缓冲区应足够容纳完整的 ANSI 转义序列
初始化过程
xwsh_cherryrl_init() 函数(readline.c:125)完成以下步骤:
-
配置初始化结构体
chry_readline_init_t init = { .prompt = xwsh_cherryrl_prompt, .pptsize = sizeof(xwsh_cherryrl_prompt), .history = xwsh_cherryrl_history, .histsize = sizeof(xwsh_cherryrl_history), .sget = xwsh_cherryrl_sget, // 输入回调 .sput = xwsh_cherryrl_sput, // 输出回调 }; -
初始化 CherryRL
rc = chry_readline_init(&xwsh_cherryrl, &init); if (0 != rc) { XwshLogE("chry_readline_init() ... <%ld>\n", rc); } -
设置回调函数
chry_readline_set_completion_cb(&xwsh_cherryrl, xwsh_cherryrl_acb); chry_readline_set_user_cb(&xwsh_cherryrl, xwsh_cherryrl_ucb); -
配置彩色提示符
// 绿色 "xwsh" chry_readline_prompt_edit(&xwsh_cherryrl, 0, (chry_readline_sgr_t){ .foreground = CHRY_READLINE_SGR_GREEN, .bold = 1, }.raw, "xwsh"); // 白色 "@" chry_readline_prompt_edit(&xwsh_cherryrl, 1, 0, "@"); // 蓝色 "xwos" chry_readline_prompt_edit(&xwsh_cherryrl, 2, (chry_readline_sgr_t){ .foreground = CHRY_READLINE_SGR_BLUE, .bold = 1, }.raw, "xwos"); // 白色 ":>" chry_readline_prompt_edit(&xwsh_cherryrl, 3, 0, ":>");
最终提示符效果:xwsh@xwos:>
回调函数详解
1. 用户事件回调(xwsh_cherryrl_ucb())
位置:readline.c:39
功能:处理 CherryRL 产生的特殊事件,如信号、功能键等。
int xwsh_cherryrl_ucb(chry_readline_t * rl, uint8_t exec)
{
/* 用户事件回调不会自动输出换行 */
chry_readline_newline(rl);
switch (exec) {
case CHRY_READLINE_EXEC_EOF:
XwshLogI("EOF\r\n");
break;
case CHRY_READLINE_EXEC_SIGINT:
chry_readline_ignore(rl, false);
XwshLogI("SIGINT\r\n");
break;
case CHRY_READLINE_EXEC_SIGQUIT:
XwshLogI("SIGQUIT\r\n");
break;
case CHRY_READLINE_EXEC_SIGCONT:
XwshLogI("SIGCONT\r\n");
break;
case CHRY_READLINE_EXEC_SIGSTOP:
XwshLogI("SIGSTOP\r\n");
break;
case CHRY_READLINE_EXEC_SIGTSTP:
XwshLogI("SIGTSTP\r\n");
break;
case CHRY_READLINE_EXEC_F1 ... CHRY_READLINE_EXEC_F12:
XwshLogI("F%d event\r\n", exec - CHRY_READLINE_EXEC_F1 + 1);
break;
default:
XwshLogI("ERROR EXEC %d\r\n", exec);
return -1; // 错误,终止 readline
}
/* 返回值含义:
* 1: 不刷新显示
* 0: 刷新整行(包括提示符)
* -1: 终止 readline(错误)
*/
return 0;
}
支持的事件:
| 事件常量 | 值 | 触发条件 | 处理方式 |
|---|---|---|---|
CHRY_READLINE_EXEC_EOF |
0 | 文件结束(Ctrl+D) | 记录日志 |
CHRY_READLINE_EXEC_SIGINT |
1 | 中断(Ctrl+C) | 重置忽略标志 |
CHRY_READLINE_EXEC_SIGQUIT |
2 | 退出(Ctrl+\) | 记录日志 |
CHRY_READLINE_EXEC_SIGCONT |
3 | 继续信号 | 记录日志 |
CHRY_READLINE_EXEC_SIGSTOP |
4 | 停止信号 | 记录日志 |
CHRY_READLINE_EXEC_SIGTSTP |
5 | 终端停止(Ctrl+Z) | 记录日志 |
CHRY_READLINE_EXEC_F1…F12 |
16-27 | 功能键 F1-F12 | 记录日志 |
2. 自动补全回调(xwsh_cherryrl_acb())
位置:readline.c:78
功能:提供命令和参数的自动补全建议(当前为空实现,预留扩展接口)。
uint8_t xwsh_cherryrl_acb(chry_readline_t * rl, char * pre,
uint16_t * size, const char ** argv,
uint8_t * argl, uint8_t argcmax)
{
XWOS_UNUSED(rl);
XWOS_UNUSED(pre);
XWOS_UNUSED(size);
XWOS_UNUSED(argv);
XWOS_UNUSED(argl);
XWOS_UNUSED(argcmax);
return 0; // 无补全建议
}
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
rl |
chry_readline_t * |
CherryRL 实例 |
pre |
char * |
需要补全的前缀字符串 |
size |
uint16_t * |
补全建议数量(输出) |
argv |
const char ** |
补全建议字符串数组(输出) |
argl |
uint8_t * |
各建议字符串长度(输出) |
argcmax |
uint8_t |
最大补全建议数 |
扩展示例(实现命令补全):
uint8_t xwsh_cherryrl_acb(chry_readline_t * rl, char * pre,
uint16_t * size, const char ** argv,
uint8_t * argl, uint8_t argcmax)
{
xwsz_t i;
uint8_t count = 0;
// 补全内置命令
for (i = 0; i < xwsh_cmd_table_size; i++) {
if (strncmp(pre, xwsh_cmd_table[i].name, strlen(pre)) == 0) {
if (count < argcmax) {
argv[count] = xwsh_cmd_table[i].name;
argl[count] = strlen(xwsh_cmd_table[i].name);
count++;
}
}
}
// 补全外部命令
if (xwsh_ext_cmd_table) {
for (i = 0; i < xwsh_ext_cmd_table_size; i++) {
if (strncmp(pre, xwsh_ext_cmd_table[i].name, strlen(pre)) == 0) {
if (count < argcmax) {
argv[count] = xwsh_ext_cmd_table[i].name;
argl[count] = strlen(xwsh_ext_cmd_table[i].name);
count++;
}
}
}
}
*size = count;
return 0;
}
3. 流输出回调(xwsh_cherryrl_sput())
位置:readline.c:91
功能:将 CherryRL 生成的输出发送到标准输出。
uint16_t xwsh_cherryrl_sput(chry_readline_t * rl, const void * data, uint16_t size)
{
int ret;
const char * buf;
(void)rl;
buf = data;
if (size > 0) {
ret = fwrite(buf, size, 1, stdout); // 写入 stdout
if (ret > 0) {
fflush(stdout); // 立即刷新
} else {
ret = 0;
}
} else {
ret = 0;
}
return (uint16_t)ret;
}
特点:
- 支持 ANSI 转义序列输出
- 立即刷新缓冲区,确保实时显示
- 返回实际写入的字节数
4. 流输入回调(xwsh_cherryrl_sget())
位置:readline.c:111
功能:从标准输入读取数据供 CherryRL 处理。
uint16_t xwsh_cherryrl_sget(chry_readline_t * rl, void * data, uint16_t size)
{
int ret;
char * buf;
(void)rl;
buf = data;
ret = fread(buf, size, 1, stdin); // 从 stdin 读取
if (ret < 0) {
ret = 0;
}
return (uint16_t)ret;
}
注意:
- 使用阻塞读取,等待用户输入
- 需要终端支持标准输入(如串口、虚拟控制台)
- 返回实际读取的字节数
历史记录实现
环形缓冲区原理
CherryRL 使用环形缓冲区存储历史记录,大小为 XWSH_RL_HISTORY_MAXSIZE(1024 字节)。
工作原理:
初始状态:[空]
记录 "cmd1":["cmd1"]
记录 "cmd2":["cmd1", "cmd2"]
...
缓冲区满:覆盖最旧记录
操作方式:
- 上箭头:浏览历史记录中的上一条命令
- 下箭头:浏览历史记录中的下一条命令
- Enter:将当前命令加入历史记录
历史记录管理
// 读取历史记录中的命令
char * prev_cmd = chry_readline_history_prev(&xwsh_cherryrl);
// 清除历史记录
chry_readline_history_clear(&xwsh_cherryrl);
// 获取历史记录数量
uint16_t hist_count = chry_readline_history_count(&xwsh_cherryrl);
行读取接口
xwsh_cherryrl_readline()
位置:readline.c:161
功能:读取一行用户输入,处理编辑操作。
char * xwsh_cherryrl_readline(char buffer[])
{
char linebuf[XWSH_MAXINPUT]; // 128字节临时缓冲区
char * line;
xwu16_t linesize;
char * ret;
line = chry_readline(&xwsh_cherryrl, linebuf, XWSH_MAXINPUT, &linesize);
if (line == NULL) {
XwshLogE("chry_readline() ... string error\n");
ret = NULL;
} else if (line == (void *)-1) {
// 取消输入(如 Ctrl+C)
chry_readline_erase_line(&xwsh_cherryrl);
chry_readline_edit_refresh(&xwsh_cherryrl);
buffer[0] = '\0';
ret = buffer;
} else if (linesize) {
// 正常输入
memcpy(buffer, line, linesize);
buffer[linesize] = '\0';
ret = buffer;
} else {
// 空输入
buffer[0] = '\0';
ret = NULL;
}
return ret;
}
返回值处理:
| 返回值 | 含义 | 处理 |
|---|---|---|
NULL |
错误 | 记录错误日志 |
(void *)-1 |
取消输入 | 清空行并刷新显示 |
| 有效指针 | 正常输入 | 复制到缓冲区并添加 \0 |
ANSI 转义序列支持
CherryRL 支持完整的 ANSI 转义序列,XWSH 利用此功能实现彩色提示符。
常用转义序列
| 序列 | 功能 | 示例 |
|---|---|---|
\e[2J |
清除整个屏幕 | clear 命令使用 |
\e[K |
清除从光标到行尾 | 行编辑时使用 |
\e[0;0H |
光标移动到 (0,0) | clear 命令使用 |
\e[1;32m |
绿色粗体文本 | 提示符 “xwsh” |
\e[1;34m |
蓝色粗体文本 | 提示符 “xwos” |
\e[0m |
重置所有属性 | 提示符后缀 |
颜色配置结构体
chry_readline_sgr_t sgr = {
.foreground = CHRY_READLINE_SGR_GREEN, // 前景色:绿色
.background = CHRY_READLINE_SGR_BLACK, // 背景色:黑色
.bold = 1, // 粗体
.italic = 0, // 非斜体
.underline = 0, // 无下划线
.blink = 0, // 不闪烁
.reverse = 0, // 不反色
};
uint32_t sgr_raw = sgr.raw; // 转换为原始值
性能优化
1. 缓冲区复用
// 静态缓冲区,避免动态分配
static char xwsh_cherryrl_prompt[XWSH_RL_PROMPT_MAXSIZE];
static char xwsh_cherryrl_history[XWSH_RL_HISTORY_MAXSIZE];
2. 减少系统调用
- 使用
fflush(stdout)确保输出及时显示 - 批量处理输入输出,减少上下文切换
- 避免在回调函数中执行耗时操作
3. 内存占用优化
| 组件 | 内存大小 | 优化措施 |
|---|---|---|
| 提示符缓冲区 | 64 字节 | 静态分配,避免碎片 |
| 历史记录缓冲区 | 1024 字节 | 环形缓冲区,固定大小 |
| CherryRL 实例 | ~100 字节 | 结构体成员紧凑排列 |
| 临时缓冲区 | 128 字节 | 栈分配,自动释放 |
调试支持
日志输出
#include <xwos/lib/xwlog.h>
#define LOGTAG "XWSH.RL"
/* #define XWSH_DBG */
#if defined(XWSH_DBG)
# define XwshLogD(fmt, ...) xwlogf(D, LOGTAG, fmt, ##__VA_ARGS__)
#else
# define XwshLogD(fmt, ...)
#endif
#define XwshLogI(fmt, ...) xwlogf(I, LOGTAG, fmt, ##__VA_ARGS__)
#define XwshLogE(fmt, ...) xwlogf(E, LOGTAG, fmt, ##__VA_ARGS__)
调试模式启用
取消 readline.c:23 的注释可启用详细调试输出:
#define XWSH_DBG // 取消注释启用调试
兼容性说明
终端要求
- 支持 ANSI 转义序列:大多数现代终端支持
- UTF-8 编码:支持多字节字符(如有需要)
- 标准输入输出:需要完整的 stdio 支持
平台限制
- 无动态内存:适合资源受限的嵌入式环境
- 单线程访问:回调函数非线程安全
- 阻塞 I/O:依赖阻塞式标准输入
扩展建议
- 自定义补全:实现
xwsh_cherryrl_acb()提供智能补全 - 颜色主题:修改
chry_readline_prompt_edit()调整配色 - 多行编辑:扩展支持多行命令输入
- 宏定义:添加命令宏和别名功能