用readline库改进REPL交互体验¶
The GNU Readline library provides a set of functions for use by applications that allow users to edit command lines as they are typed in.
引言¶
GNU readline
库是广泛使用的C语言库,在Emacs
和Vi
中都有使用到:
- https://tiswww.case.edu/php/chet/readline/rltop.html
- https://tiswww.case.edu/php/chet/readline/readline.html
这篇将使用此库建立一个简单的REPL,具备一定的交互能力,然后通过默认配置文件以支持中文输入。
Python中的readline¶
在Python中就自带了这个库的对应版本:
而比Python官方REPL更好用的IPython
最初是有一个类似readline功能的前端模块,后来替换为:
python-prompt-toolkit
: https://github.com/prompt-toolkit/python-prompt-toolkitripython
: https://github.com/ipython/rlipython
这两个库在readline库基础上让REPL和对应语言语法结合起来,有着更好的代码高亮、提示、补全、跨行输入等特性。
简陋的REPL¶
std::string line;
while (true) {
std::cout << ">>";
getline(std::cin, line);
std::cout << line << std::endl;
}
这就是最简单的REPL了,有提示符,也可以获取输入,但存在问题:
- 没有命令提示
- 没有历史命令
- 光标无法移动,只能挨个删除输入字符
引入readline库¶
#include <readline/readline.h>
std::string line;
while (true) {
char *line_read = readline(">>");
line = line_read;
free(line_read);
std::cout << line << std::endl;
}
通过调用readline获取输入,就自动有了光标移动等特性,每次获取输入后要通过free释放内存。
历史命令¶
REPL退出时将历史命令记录到文件中,然后每次启动的时候自动加载历史命令文件:
#include <readline/readline.h>
#include <readline/history.h>
std::string history = ".ledge_history";
stifle_history(100); // 最多记录100条
read_history(history.c_str());
std::string line;
while (true) {
char *line_read = readline(">>");
if (line_read && *line_read) {
add_history(line_read);
}
line = line_read;
free(line_read);
std::cout << line << std::endl;
}
write_history(history.c_str());
通过3个历史命令api完成了读取、添加、保存历史命令文件功能。
自动补全¶
默认自动补全是按Tab键用本地文件或目录来补全,可以定制为根据已输入内容进行语法补全:
void initialize_readline() {
// .inputrc里面可以使用类似语法
// $if ledge ..... $endif
rl_readline_name = "ledge";
// 注册自动补全回调函数
rl_attempted_completion_function = ledge_completion;
}
char **ledge_completion(const char *text, int start, [[maybe_unused]] int end) {
char **matches;
matches = nullptr;
// 行首部分作为命令,剩下部分用本地文件名补全
if (start == 0) {
matches = rl_completion_matches(text, command_generator);
}
else {
matches = rl_completion_matches(text, rl_filename_completion_function);
}
return (matches);
}
// 不断尝试符合条件的命令,得到一个集合,到最后一个空字符串表示查找结束
char *command_generator(const char *text, int state) {
static int list_index, len;
std::string name;
// 开始查找
if (!state) {
list_index = 0;
len = strlen(text);
}
int size = commands.size();
while (list_index < size) {
name = commands[list_index];
if (name == "") {// 结束查找
return nullptr;
}
list_index++;
if (strncmp(name.c_str(), text, len) == 0)
return (dupstr(name.c_str()));
}
// 结束查找
return nullptr;
}
自动补全后,还可以预定义回调函数进一步执行相应操作。
readline无法输入中文¶
readline
库会自动读取配置文件(~/.inputrc
):
# 允许8bit输入
set meta-flag on
set input-meta on
# 禁止自动去除最高位
set convert-meta off
# 允许最高位显示
set output-meta on
# 按键配置
"\eOA": history-search-backward
"\eOB": history-search-forward
"\e[A": history-search-backward
"\e[B": history-search-forward
"\eOC": forward-char
"\eOD": backward-char
"\e[C": forward-char
"\e[D": backward-char
vim无法显示中文¶
配置文件(~/.vimrc
):
emacs无法显示中文¶
配置文件(~/.emacs.el
):
- 微信搜索: 「 MinYiLife 」, 关注公众号!
- 本文链接: https://www.lesliezhu.com/blog/2023/01/02/gnu_readline_in_repl/
- 版权声明: 原创文章,如需转载请注明文章作者和出处。谢谢!