快捷键和控制序列--自己实现vi

来源:互联网 发布:鼎豪网络 编辑:程序博客网 时间:2024/05/01 10:09

从《快捷键和控制序列--bash的命令行编辑原理以及其它杂述》中已经明白了按键和控制序列的原理,现在可以猜一下vi的原理了,大体上vi可能会包含类似下面的结构,也就是一些键映射,其中有一个命令映射和一个插入映射:
KEYMAP map_cmd = {
    {'w',func_cmd_w},
    {'b',func_cmd_b},
    ...
}
KEYMAP map_inst = {
    {'w',func_inst_w},
    {'b',func_inst_b},
    ...
}
...
可是看了vi的源代码之后发现并不是这回事,normal函数够看一阵了了,edit函数也够喝一壶的,不过这两个函数的逻辑都很简单的。好了,既然vi不是上面猜测的那样,可是我还是不甘心,上面的那个猜测多好啊,多么OO啊,不用岂不可惜,于是就着这个猜测自己实现一个vi,虽然简陋但是可以说明问题:
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
typedef int Function ();
typedef struct key {
    char c;                            //输入的字符
    Function *function;                //对应该字符的处理函数
}KEY;
char bufline[1024] = {0};            //简化起见,只编辑一行,不处理换行回车符
int pos = 0;                        //光标的当前位置
int a_ifunc();                        //这里定义一大堆函数,用来处理字符输入
...
KEY key_i[] = {
    {'a', a_ifunc},
    {'b', b_ifunc},
    {'c', c_ifunc},
    {'d', d_ifunc},
};
KEY key_c[] = {
    {'a', a_cfunc},
    {'b', b_cfunc},
    {'c', c_cfunc},
    {'d', d_cfunc},
};
KEY *cur = key_i;
int set_position(row, line)    //设置光标的位置,vt100兼容终端下可用
{
    char cmd[32] = {0};
    sprintf(cmd, "/033[%d;%dH", row, line);
    printf(cmd);
}
int a_ifunc()            //插入模式下的字符a处理函数
{
    bufline[pos] = 'a';
    pos ++;
    set_position(1,0);
    printf("%s", bufline);    //注意不要最后写/n换行符,否则光标就“回车并换行”了,光标的列总是0,而行总是需要的下一行
    set_position(1,pos);
    return 1;
}
int b_ifunc()            //插入模式下的字符b处理函数
{
    bufline[pos] = 'b';
    pos ++;
    set_position(1,0);
    printf("%s", bufline);
    set_position(1,pos);
    return 1;
}
int c_ifunc()            //插入模式下的字符c处理函数
{
    bufline[pos] = 'c';
    pos ++;
    set_position(1,0);
    printf("%s", bufline);
    set_position(1,pos);
    return 1;
}
int d_ifunc()            //插入模式下的字符d处理函数,切换模式
{
    cur = key_c;   
    return 1;
}
int a_cfunc()            //命令模式下的字符a处理函数,前移光标
{
    char cmd[64] = {0};
    pos ++;
    set_position(1,pos);
    return 1;
}
int b_cfunc()            //命令模式下的字符b处理函数,后退光标
{
    char cmd[64] = {0};
    pos --;
    if (pos <= 0)
        pos = 0;
    set_position(1,pos);
    return 1;
}
int c_cfunc()            //命令模式下的字符c处理函数,自定义
{
    ...
    return 1;
}
int d_cfunc()            //命令模式下的字符d处理函数,切换模式
{
    cur = key_i;
    return 1;
}
int main(int argc, char **argv)
{
    char c;                   //定义键盘输入的字符
    struct termios s, t;            //终端设置,主要是为了取消回显
    FILE *in = fopen ("/dev/tty", "w+");      //这个代码是从coreutils中的getpass函数中复制下来的(读源码的好处)
    if (tcgetattr (fileno (in), &t) == 0) {
        t.c_lflag &= ~(ECHO|ISIG);
        int tty_changed = (tcsetattr (fileno (in), TCSAFLUSH, &t) == 0);
    }
    printf("/033[2J/n" );        //学习vi,清屏,配置一个好的试验场
    set_position(1,0);        //光标设置到最上面的一行开始
    memset(bufline, 32, 1024);    //将缓冲区设置成空格,方便使用printf打印,否则全部初始化为0的话就要逐字符打印了
    while (1) {            //何时退出呢?输入一个除了a,b,c,d之外的就段错误了...
        c = fgetc(stdin);    //简化设计,因此该简陋的vi每输入一个字符后需要敲入一个回车
        if (c != 10) {        //忽略换行
            cur[c-'a'].function(); //处理
        }
    }
}
编译它:
gcc minivi.c -o minivi
然后执行minivi,输入一下,切换一下,再输入一下...输入一个a,b,c,d之外的字符就以段错误退出,并且minivi只能处理4个字符只有两个模式,这问题决定了minivi是一个很值得扩展的程序,大框架在此,懒得扩展了,骨架好身材就好,不在乎肉多肉少

原创粉丝点击