Lettershell之输入缓冲区

lettershell项目链接 https://github.com/NevermindZZT/letter-shell

更多精彩内容欢迎关注微信公众号:码农练功房
在这里插入图片描述
往期精彩内容:
Lettershell之移植篇
Lettershell之命令注册
Lettershell之按键识别

输入缓冲区在Lettershell中扮演着十分关键的角色,它有如下重要作用:

  1. 暂存输入:在用户完全键入一条命令之前,输入缓冲区会存储用户的输入,直到用户按下回车键提交命令。
  2. 编辑能力:输入缓冲区通常支持基本的编辑功能,如向前或向后移动光标、删除字符等。
  3. 历史记录:用户可以通过向上箭头键访问先前输入的命令,这是通过维护一个输入历史记录列表实现的,而输入缓冲区是这个过程的一部分。
  4. 自动补全:现代的命令行界面通常支持命令或文件名补全功能,这需要访问当前输入的内容,因此输入缓冲区是非常重要的。

数据结构

输入缓冲区在Lettershell中的定义如下(由于历史记录和输入缓冲区有着紧密关系,所以一并列出):

typedef struct shell_def
{
    // ......
    struct
    {
        unsigned short length;                     /**< 输入数据长度 */
        unsigned short cursor;                     /**< 当前光标位置 */
        char *buffer;                              /**< 输入缓冲 */
        char *param[SHELL_PARAMETER_MAX_NUMBER];   /**< 参数 */
        unsigned short bufferSize;                 /**< 输入缓冲大小 */
        unsigned short paramCount;                 /**< 参数数量 */
        int keyValue;                              /**< 输入按键键值 */
    } parser;
#if SHELL_HISTORY_MAX_NUMBER > 0
    struct
    {
        char *item[SHELL_HISTORY_MAX_NUMBER];      /**< 历史记录 */
        unsigned short number;                     /**< 历史记录数 */
        unsigned short record;                     /**< 当前记录位置 */
        signed short offset;                       /**< 当前历史记录偏移 */
    } history;
#endif /** SHELL_HISTORY_MAX_NUMBER > 0 */
   // ......
} Shell;

初始化

shellInit函数中进行输入缓冲区的初始化:

#define     SHELL_HISTORY_MAX_NUMBER    5
char shellBuffer[512];
//......
void shellInit(Shell *shell, char *buffer, unsigned short size)
{
    // ......
    shell->parser.length = 0;
    shell->parser.cursor = 0;
    shell->parser.buffer = buffer;
    shell->parser.bufferSize = size / (SHELL_HISTORY_MAX_NUMBER + 1);
#if SHELL_HISTORY_MAX_NUMBER > 0
    shell->history.offset = 0;
    shell->history.number = 0;
    shell->history.record = 0;
    for (short i = 0; i < SHELL_HISTORY_MAX_NUMBER; i++)
    {
        shell->history.item[i] = buffer + shell->parser.bufferSize * (i + 1);
    }
#endif
    // ......
}
// ......
shellInit(&shell, shellBuffer, 512);

根据代码,我们不难得出下图:

请添加图片描述

在当前配置下,输入缓冲区shellBuffer被划分成了6个区域,每个区域占用85字节。

其中第一个85字节的空间用来存储用户当前输入内容其余空间用来储存历史记录,目前最多可以保存5条历史记录。

对输入缓冲区的维护,其实就是对输入缓冲区数据的增删改查,而为了实现增删改查,则需要管理好如下索引(变量):

shell->parser.length;
shell->parser.cursor;
shell->history.offset;
shell->history.number;
shell->history.record;

数据增加

数据增加分成两种情况,一种是尾部插入(这里维护了parser.length和parser.cursor这两个计数器):

void shellInsertByte(Shell *shell, char data)
{   
	// ......
    if (shell->parser.cursor == shell->parser.length)
    {
     //   printf("---\n");
        shell->parser.buffer[shell->parser.length++] = data;
        shell->parser.buffer[shell->parser.length] = 0;
        shell->parser.cursor++;
        shellWriteByte(shell, shell->status.isChecked ? data : '*');
    }
    // ......
}

还有一种情况是通过使用光标在指定位置插入。这种情况的处理由两个步骤组成:

  1. 识别到左右方向键后,回调对应按键处理函数。
  2. 指定位置插入字符。

先看第一个步骤,这里回调了一开始注册的按键处理函数:

/**
 * @brief shell右方向键输入
 */
void shellRight(Shell *shell)
{
    if (shell->parser.cursor < shell->parser.length)
    {
        shellWriteByte(shell, shell->parser.buffer[shell->parser.cursor++]);
    }
}
/**
 * @brief shell左方向键输入
 */
void shellLeft(Shell *shell)
{
    if (shell->parser.cursor > 0)
    {
        // '\b'为退格(BS),将当前位置移到前一列
        shellWriteByte(shell, '\b');
        shell->parser.cursor--;
    }
}

主要就是在维护parser.cursor计数器,同时向终端工具发送字符,控制显示。

第二个步骤对应我们输入动作,处理如下:

void shellInsertByte(Shell *shell, char data)
{
    // ......
    else if (shell->parser.cursor < shell->parser.length)
    {
        // 插入位置之后的所有元素向后移动一位
        for (short i = shell->parser.length - shell->parser.cursor; i > 0; i--)
        {
            shell->parser.buffer[shell->parser.cursor + i] = 
                shell->parser.buffer[shell->parser.cursor + i - 1];
        }
        shell->parser.buffer[shell->parser.cursor++] = data;
        shell->parser.buffer[++shell->parser.length] = 0;
        // 控制终端工具回显
        for (short i = shell->parser.cursor - 1; i < shell->parser.length; i++)
        {
            shellWriteByte(shell, 
                           shell->status.isChecked ? shell->parser.buffer[i] : '*');
        }
        for (short i = shell->parser.length - shell->parser.cursor; i > 0; i--)
        {
            shellWriteByte(shell, '\b');
        }
    }
    // ......
}

由于不是在数组尾部插入数据,所以在插入位置之后的所有元素需要向后移动一位,然后再控制终端工具正确回显。

数据删除

数据删除也分成两种情况,一种是从尾部删除,一旦我们按下backspace,便回调之前注册的函数:

/**
 * @brief shell 退格
 * 
 * @param shell shell对象
 */
void shellBackspace(Shell *shell)
{
    shellDeleteByte(shell, 1);
}
 // 删除方向 {@code 1}删除光标前字符 {@code -1}删除光标处字符
void shellDeleteByte(Shell *shell, signed char direction)
{
    // ......
    if (shell->parser.cursor == shell->parser.length && direction == 1)
    {
        shell->parser.cursor--;
        shell->parser.length--;
        shell->parser.buffer[shell->parser.length] = 0;
        shellDeleteCommandLine(shell, 1);
    }
     // ......
}

void shellDeleteCommandLine(Shell *shell, unsigned char length)
{
    while (length--)
    {
        shellWriteString(shell, "\b \b");
    }
}

还有一种情况是通过使用光标删除指定位置数据,这里光标控制在前一节已经有介绍了,不再赘述:

void shellDeleteByte(Shell *shell, signed char direction)
{
    // ......
    else
    {
        for (short i = offset; i < shell->parser.length - shell->parser.cursor; i++)
        {
            shell->parser.buffer[shell->parser.cursor + i - 1] = 
                shell->parser.buffer[shell->parser.cursor + i];
        }
        shell->parser.length--;
        if (!offset)
        {
            shell->parser.cursor--;
            shellWriteByte(shell, '\b');
        }
        shell->parser.buffer[shell->parser.length] = 0;
        for (short i = shell->parser.cursor; i < shell->parser.length; i++)
        {
            shellWriteByte(shell, shell->parser.buffer[i]);
        }
        shellWriteByte(shell, ' ');
        for (short i = shell->parser.length - shell->parser.cursor + 1; i > 0; i--)
        {
            shellWriteByte(shell, '\b');
        }
    }
}

由于不是在数组尾部删除数据,所以在删除位置之后的所有元素需要向前移动一位,然后再控制终端工具正确回显。

数据修改

数据修改其实就是增加、删除的复合操作,这里不重复介绍了。

数据查询

命令自动补全,还有历史记录这两个功能涉及到输入缓冲区数据查询。限于篇幅,此处不详细展开,后面文章我们会有介绍。

总结

  1. 从代码上看,缓冲区的管理逻辑被分散在多个函数中,阅读代码时,我们需要在多个函数间跳转才能看到全貌。
  2. 除了需要维护好缓冲区外,还需要在增删改查数据的同时维护好回显。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值