linenoise 源码分析(一)

news/2024/7/10 18:44:00 标签: c语言, 开源

linenoise是一个命令行编辑库(line editing library),由redis数据库设计者Antirez发布一个用于颠覆“一个命令行编辑库需要20000行C代码”的观点。目前linenoise已经使用在Redis, MongoDB,Android等应用上。其支持特性如下:

1) 尽可能简小,无配置,支持BSD license。

2) 单行或多行编辑模式,使用常用的快捷键绑定实现。

3)支持历史键入文本查询。

4)兼容性。

5)大约只有1100行代码,支持BSD license。

6)只使用VT100 escapes键码的子集。

我阅读了linenoise源码,非常简单,非常简练,其中也有很多值得学习的东西,于是将这些东西记录下来,供自己使用,也供各位参考。


一、 文件构成

linenoise库文件非常少,没有子目录,其中的核心文件仅仅是linenoise.c 和linenoise.h,还有一个example.c,作为使用的教学程序。当然,还有一个makefile和README.markdown,此处我只分析核心的linenoise.c和linenoise.h。


二、数据结构

(1)linenoiseCompletions:

typedef struct linenoiseCompletions {
  size_t len;
  char **cvec;
} linenoiseCompletions;

此结构用于帮助实现用户输入自动补全功能。就像我们使用linux命令行终端一样,当我们键入命令头几个字母,使用tab键便可以完成自动补全或查找匹配,或者我们指定cd路径,输入路径头几个字符,tab后完成后面的补全工作。而linenoise实现的也是这个功能,但真正的匹配工作需要你注册函数完成,就是linenoise中的completionCallback。

此结构体只有两个导出方法,第一个是:

void linenoiseAddCompletion(linenoiseCompletions *, const char *);
将字符串指针添加到结构体的cvec变量。这个过程主要通过memmove() 和 realloc() 两个函数完成。
第二个函数:

typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); 

注册用于完成匹配工作的函数,仅仅是个简单的指针赋值,指针存储在一个全局变量中。

非导出函数:

static void freeCompletions (linenoiseCompletions *lc);
回首指定的结构体linenoiseCompetions,仅仅回首其中的char** cvec和减少len字段的值。循环len次调用free()回首所有的字符串指针,再回收一次cvec。

static int completeLine(struct linenoiseState *ls)
这是自动补全功能的核心函数。参数为linenoise库状态结构体,这个结构体记录了当前用户的输入状态,用于控制输入输出,是整个库的核心。此函数提供给内部函数linenoiseEdit()调用,用于处理用户输入的tab键,并完成自动补全。当然,这个函数必须在用户已经注册linenoiseCallback后才能使用。处理过程如下:

a)初始化一个局部变量linenoiseCompletions lc,调用回调函数匹配用户没有补全的输入,并将查找到的结果填充到lc。

b)若结果为空,beep一次,否则进入循环输入处理,循环将用户输入分三类处理,tab,esc和其他。

c)循环查找lc中的匹配命令,若已经完成一次遍历,调用refreshLine (),此函数只是调整linenoiseState结构体,并不输出。若还有剩余元素需要遍历,存储state当前状态,将匹配到的命令存入state,再refreshLine(),最后再还原。

d)接着读取标准输入的一个字符,失败则调用freeCompletions()回收lc,否则switch成三类按键

若是tab键,将lc中数组的循环变量加1,这里不是简单加1,公式如下:

i = (i+1) % (lc.len+1);
i是循环变量,其取值范围控制在[ 0, lc.len  ]范围,这样当i取到最大值时,下次循环会再次回绕到最小值,保证了不停的循环这个lc.cvec数组。也就是我们在命令行不停按tab时提供的匹配功能。当i取lc.len时,判断已经完成一次循环迭代。

若按的是escape(ESC)键,则先检测是否已经迭代完了,迭代完则调整一次state,refreshLine ()。最后跳出循环。

若是按其他键,则将查找到的结果存入state,并调整,最后跳出循环。

e)回收lc,将d)中read得到的字符作为返回值输出。

以上描述可能很冗杂,下面是源代码,对照看即可:

/* This is an helper function for linenoiseEdit() and is called when the
 * user types the <tab> key in order to complete the string currently in the
 * input.
 *
 * The state of the editing is encapsulated into the pointed linenoiseState
 * structure as described in the structure definition. */
static int completeLine(struct linenoiseState *ls) {
    linenoiseCompletions lc = { 0, NULL };
    int nread, nwritten;
    char c = 0;

		// ls -> buf stored user typed command.
    completionCallback(ls->buf,&lc); // found completion.
    if (lc.len == 0) { // find nothine !
        linenoiseBeep();
    } else {
        size_t stop = 0, i = 0;

        while (!stop) {
            /* Show completion or original buffer */
            if (i < lc.len) {
                struct linenoiseState saved = *ls;

                ls->len = ls->pos = strlen(lc.cvec[i]);
                ls->buf = lc.cvec[i];
                refreshLine(ls); // do not print to stdout.
                ls->len = saved.len;
                ls->pos = saved.pos;
                ls->buf = saved.buf;
            } else {
                refreshLine(ls);
            }

            nread = read(ls->ifd,&c,1);
            if (nread <= 0) {
                freeCompletions(&lc);
                return -1;
            }

            switch(c) {
                case 9: /* tab */
                    i = (i+1) % (lc.len+1);
                    if (i == lc.len) linenoiseBeep();
                    break;
                case 27: /* escape */
                    /* Re-show original buffer */
                    if (i < lc.len) refreshLine(ls);
                    stop = 1;
                    break;
                default:
                    /* Update buffer and return */
                    if (i < lc.len) {
                        nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
                        ls->len = ls->pos = nwritten;
                    }
                    stop = 1;
                    break;
            }
        }
    }

    freeCompletions(&lc);
    return c; /* Return last read character */
}

(2)结构体abuf,实现成最简单的append buffer,是一个heap allocated string。这个结构体很简单,只有一个char*变量作为buffer,还有一个len字段标示buffer当前长度。

这个结构体作为库的内部使用,不向外导出。主要用于refreshLine函数,进行字符串追加操作。所以提供的函数也很简单:

static void abInit(struct abuf *ab);
初始化,不调用malloc(),仅在append时才调用realloc(),分配内存。

static void abAppend(struct abuf *ab, const char *s, int len);
将s字符串追加到ab中,len指定了s长度。此函数只是简单调用realloc()调整分配的内存大小和memcpy()进行数据复制。

static void abFree (struct abuf *ab)
回收一个abuf的结构体实例,此方法并不回收abuf,只是回收abuf 中的char* 缓冲区。


以上是两个内部结构体在库中的用途和接口分析,还有一个核心linenoiseState,我在下一篇博客中分析。


http://www.niftyadmin.cn/n/983450.html

相关文章

imgaug quokka_Quokka CMS的新功能和Beta版路线图

imgaug quokkaI started the development of Quokka CMS in 6th July, and it started as an experiment to learn more about Flask patterns and MongoDB. From the learning experiment I ended creating an extendable CMS. 我从7月6日开始开发Quokka CMS &#xff0c;并将…

暑期2021来啦!优秀开源项目技术大咖一对一指导,全面提升你的实战能力

还记得去年的“开源软件供应链点亮计划-暑期2020”吗&#xff1f;去年有三位同学参与了TDengine社区的项目&#xff0c;他们与TDengine的故事&#xff0c;从这里开始。有的同学收获了充实丰富的实践经验&#xff0c;有的同学通过TDengine的项目经验找到一份不错的工作&#xff…

JavaScript的那些书

2019独角兽企业重金招聘Python工程师标准>>> 第一批次:入门级 &#xff0c;也适合想掌握一些前端技能的非前端工程师。 《JavaScript DOM 编程艺术》 第二批次:成为一名合格的前端工程师 《JavaScript高级程序设计》(或《JavaScript权威指南》) 《精通JavaScript》 …

正则表达式python_Python的隐藏正则表达式宝石

正则表达式pythonThere are many terrible modules in the Python standard library, but the Python re module is not one of them. While it’s old and has not been updated in many years, it’s one of the best of all dynamic languages I would argue. Python标准库中…

基于TDengine进行睿信物联网平台的迁移改造

作者&#xff1a;艾忠元 睿信物联网平台是北京睿信世达自主开发的通用物联网平台&#xff0c;为用户提供从传感器设备、边缘网关、云平台到APP、小程序的一整套端到端物联网SaaS云平台&#xff0c;可以满足用户的通用物联网功能需求&#xff1b;在此基础上构建了智慧水务、智慧…

404 – File or directory not found.

Server Error 404 – File or directory not found. The resource you are looking for might have been removed, had its name changed, or is temporarily unavailable. 用户上传一个大小大约为160MB的文件至服务器出现这个错误。Insus.NET把上传文件于本机测试没有问题。但…

linenoise 源码分析(二)

上一篇博客我分析了linenoise中的数据结构linenoiseCompletions和abuf&#xff0c;这篇我分析一下linenoise的历史命令取回和linenoiseState结构体。linenoiseState结构体即是这个库核心的部分&#xff0c;起到控制当前用户输入状态的功能。而history部分并不复杂&#xff0c;只…

存储成本仅为OpenTSDB的1/10,TDengine的最大杀手锏是什么?

在《这几个神秘参数&#xff0c;教你TDengine集群的正确使用方式》中&#xff0c;我们告诉了大家&#xff1a;如何才能让数据均匀的分布在节点中。接下来&#xff0c;我们和大家一起以产品使用者的视角继续向前探索。 如果说让数据均匀分布的目的是为了最大化地使用CPU资源的话…