扩展机制

XWSH 的扩展机制:CherryRL 库集成细节、回调函数说明和历史记录实现。

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)完成以下步骤:

  1. 配置初始化结构体

    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,  // 输出回调
    };
    
  2. 初始化 CherryRL

    rc = chry_readline_init(&xwsh_cherryrl, &init);
    if (0 != rc) {
            XwshLogE("chry_readline_init() ... <%ld>\n", rc);
    }
    
  3. 设置回调函数

    chry_readline_set_completion_cb(&xwsh_cherryrl, xwsh_cherryrl_acb);
    chry_readline_set_user_cb(&xwsh_cherryrl, xwsh_cherryrl_ucb);
    
  4. 配置彩色提示符

    // 绿色 "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_F1F12 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:依赖阻塞式标准输入

扩展建议

  1. 自定义补全:实现 xwsh_cherryrl_acb() 提供智能补全
  2. 颜色主题:修改 chry_readline_prompt_edit() 调整配色
  3. 多行编辑:扩展支持多行命令输入
  4. 宏定义:添加命令宏和别名功能