`
xitong
  • 浏览: 6197977 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

从《快捷键和控制序列--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是一个很值得扩展的程序,大框架在此,懒得扩展了,骨架好身材就好,不在乎肉多肉少

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics