基于clish/klish tinyrl实现的Linux类shell粘贴功能异常问题求解
Tinyrl 组件粘贴输入显示异常修复方案
问题背景
当前使用的是基于clish/klish开源生态下tinyrl组件实现输入逻辑的非Bash Linux Shell,核心输入读取函数tinyrl_vt100_getchar的现有实现如下:
tinyrl_vt100_getchar(const tinyrl_vt100_t *this) { unsigned char c='\0'; int istream_fd = -1; FILE *sfd = this->istream; if (!sfd) return VT100_ERR; istream_fd = fileno(sfd); /* Just wait for the input if no timeout */ if (this->timeout <= 0) { if ((c = getc(sfd)) == EOF) { if (feof(sfd)) return VT100_EOF; else return VT100_ERR; } return c; } /* Set timeout for the select() */ fd_set rfds; FD_ZERO(&rfds); FD_SET(istream_fd, &rfds); struct timeval tv; tv.tv_sec = this->timeout; tv.tv_usec = 0; int retval = -1; while (((retval = select(istream_fd + 1, &rfds, NULL, NULL, &tv)) < 0) && (EAGAIN == errno)); /* Error or timeout */ if (retval < 0) return VT100_ERR; if (!retval) return VT100_TIMEOUT; if ((c = getc(sfd)) == EOF) { if (feof(sfd)) return VT100_EOF; else return VT100_ERR; } return c; }
异常现象
- 向Shell执行粘贴文本操作时,界面仅显示粘贴内容的首个字符
- 必须手动按下任意按键(方向键、空格、退格键、普通字符等)后,剩余的粘贴内容才会完整渲染到界面
- 实际复现:预先复制内容
abc def xyz执行粘贴,界面仅显示首字符a,触发任意按键输入后剩余内容才会正常补全显示
已尝试方案的局限性
曾直接用read()调用替换原实现中的getc()读取逻辑,虽然可以解决粘贴内容分块显示的问题,但会引入新的故障:粘贴完成后快速移动光标时,方向键对应的VT100转义序列(如[C、[D)会被直接当作普通字符打印到界面,方案不具备落地条件。
问题根因
原代码的逻辑缺陷非常明确:检测到输入流就绪后,仅读取1个字节就返回给上层做渲染。终端执行粘贴操作时,会一次性把全部粘贴内容推到输入流的内核缓冲区,代码读走第一个字节后就直接返回上层处理,剩余的字节全堵在内核缓冲区里,直到下一次输入等待循环被用户按键触发时才会被取出来,自然就会出现要按一下键才能显示全内容的问题。
之前换read()一次读取多字节的方案为啥会出问题?本质是连续读取的节奏匹配错误:没有设置合理的读取终止条件,要么一次把后续短时间内到达的方向键转义序列提前吞入,要么把转义序列拆成单个普通字符透传给上层,最终导致转义字符乱打印、光标解析失效。
可落地的修复思路
- 完全保留原
select()等待输入就绪的核心逻辑,不改动原有超时判断的分支逻辑 - 当输入流被
select()判定为就绪后,不要仅读取1个字节就立刻返回上层:在当前轮次用10ms以内的极短超时,循环读取输入缓冲区中已经存在的全部字节,将这些字节存入与当前Shell上下文绑定的输入缓存队列中 - 从缓存队列中逐个取出字节返回给上层做正常的输入解析和界面渲染,确保粘贴的全部内容会被一次性取出,不需要等待下一次按键即可完整显示
- 针对方向键的VT100转义序列做兼容处理:当从缓存队列取到转义起始字符
\033时,立刻停止本轮连续读取的流程,等待后续转义字符完整到达后再做解析,避免把转义序列拆成单个普通字符透传给上层 - 调整读取终止的触发条件:只要检测到输入缓冲区为空、或者读取到需要特殊解析的转义序列起始符,就立刻停止连续读取返回上层处理,不会提前吞掉用户后续的方向键按键输入,从根源上规避之前
read()方案的转义序列乱打印问题
内容的提问来源于stack exchange,提问作者Dilip
相关产品推荐
相关产品推荐

