一 行编辑的由来
在和程序交互的过程中,用户输入难免会出错,如果不小心键入失误而不能修改,那该是一种很不友好的体验,于是行编辑出现了,用户在输入过程中可以删除,粘贴,移动光标,在光标处插入,可以翻看历史命令,只有当用户确认输入是正确无误,才按回车将输入结果提交给程序。
如果行编辑功能由用户程序来实现,那么将增加用户程序编程的难度,而且很多行编辑功能基本是相似的,于是linux就把它放在了内核中,也就是常说的行规程(line discipline), 终端类型不同,对应的line discipline也不一样,用户进程每次通过tty读取line discipline处理后的一行数据,另外用户也可以通过termios配置line discipline的行为,当然了这种配置是有限的,如果用户程序希望定制自己特有的行编辑功能,如一些编辑软件vi,shell等等,可以将tty设置为raw模式,这将跳过line discipline处理直接读取输入的原始值,而将行编辑功能交由特定的用户程序来处理,如readline库就是其中一个使用非常广泛的处理行编辑的开源库。
本文主要讲解boot中行编辑功能的实现,功能较简单。
二 行编辑的实现
#define CTRL(x) ((x)-'@')
#define VKEY(x) (0x100|(x))
#define VKEY_UP VKEY(1)
#define VKEY_DOWN VKEY(2)
#define VKEY_LEFT VKEY(3)
#define VKEY_RIGHT VKEY(4)
#define VKEY_PGUP VKEY(5)
#define VKEY_PGDN VKEY(6)
#define VKEY_HOME VKEY(7)
#define VKEY_END VKEY(8)
#define VKEY_F1 VKEY(0x10)
#define VKEY_F2 VKEY(0x11)
#define VKEY_F3 VKEY(0x12)
#define VKEY_F4 VKEY(0x13)
#define VKEY_F5 VKEY(0x14)
#define VKEY_F6 VKEY(0x15)
#define VKEY_F7 VKEY(0x16)
#define VKEY_F8 VKEY(0x17)
#define VKEY_F9 VKEY(0x18)
#define VKEY_F10 VKEY(0x19)
#define VKEY_F11 VKEY(0x1A)
#define VKEY_F12 VKEY(0x1B)
#define VKEY_ESC 27
int console_readline(char *prompt,char *str,int maxlen)
{
int reading = 1;
int ch;
int idx = 0;
int len = 0;
int t;
int klen;
int recall;
int nosave = 0;
char *x;
char env[10];
console_inreadline++;
recall = console_nextsave;
if (console_savedlines[console_nextsave]) {
KFREE(console_savedlines[console_nextsave]);
console_savedlines[console_nextsave] = NULL;
}
console_savedlines[console_nextsave] = strdup("");
if (prompt && *prompt) console_write(prompt,strlen(prompt));
POLL();
while (reading) {
/*
* If someone used console_log (above) or hit Control-C (below),
* redisplay the prompt and the string we've got so far.
*/
if (console_redisplay) {
if (prompt && *prompt) console_write(prompt,strlen(prompt));
console_write(str,idx);
console_redisplay = 0;
continue;
}
/*
* if nobody's typed anything, keep polling
*/
if (console_status() == 0) {
POLL();
continue;
}
/*
* Get the char from the keyboard
*/
ch = console_readkey();
if (ch < 0) break;
if (ch == 0) continue;
/*
* And dispatch it. Lots of yucky character manipulation follows
*/
switch (ch) {
case CTRL('C'): /* Ctrl-C - cancel line */
console_write("^C\r\n",4);
console_redisplay = 1;
nosave = 1;
idx = 0;
len = 0;
break;
case 0x7f: /* Backspace, Delete */
case CTRL('H'):
if (idx > 0) {
nosave = 0;
len--;
idx--;
console_write("\b",1);
if (len != idx) {
for (t = idx; t < len; t++) str[t] = str[t+1];
console_write(&str[idx],len-idx);
console_whiteout(1);
console_backspace(len-idx);
}
else {
console_whiteout(1);
}
}
break;
case CTRL('D'): /* Ctrl-D */
if ((idx > 0) && (len != idx)) {
nosave = 0;
len--;
for (t = idx; t < len; t++) str[t] = str[t+1];
console_write(&str[idx],len-idx);
console_whiteout(1);
console_backspace(len-idx);
}
break;
case CTRL('B'): /* cursor left */
case VKEY_LEFT:
if (idx > 0) {
idx--;
console_backspace(1);
}
break;
case CTRL('F'): /* cursor right */
case VKEY_RIGHT:
if (idx < len) {
console_write(&str[idx],1);
idx++;
}
break;
case CTRL('A'): /* cursor to BOL */
console_backspace(idx);
idx = 0;
break;
case CTRL('E'): /* cursor to EOL */
if (len-idx > 0) console_write(&str[idx],len-idx);
idx = len;
break;
case CTRL('K'): /* Kill to EOL */
if (idx != len) {
str[len] = '\0';
if (console_killbuffer) KFREE(console_killbuffer);
console_killbuffer = strdup(&str[idx]);
console_whiteout(len-idx);
len = idx;
nosave = 0;
}
break;
case CTRL('Y'): /* Yank killed data */
if (console_killbuffer == NULL) break;
klen = strlen(console_killbuffer);
if (klen == 0) break;
if (len + klen > maxlen) break;
nosave = 0;
for (t = len + klen; t > idx; t--) {
str[t-1] = str[t-klen-1];
}
for (t = 0; t < klen; t++) str[t+idx] = console_killbuffer[t];
len += klen;
console_write(&str[idx],len-idx);
idx += klen;
console_backspace(len-idx-1);
break;
case CTRL('R'): /* Redisplay line */
str[len] = 0;
console_crlf();
if (prompt && *prompt) console_write(prompt,strlen(prompt));
console_write(str,len);
console_backspace(len-idx);
break;
case CTRL('U'): /* Cancel line */
console_backspace(idx);
console_eraseeol();
if (len > 0) nosave = 1;
idx = 0;
len = 0;
break;
case CTRL('M'): /* terminate */
case CTRL('J'):
console_crlf();
reading = 0;
break;
case CTRL('P'):
case VKEY_UP: /* recall previous line */
t = recall;
t--;
if (t < 0) t = MAXSAVELINES-1;
if (console_savedlines[t] == NULL) break;
recall = t;
console_backspace(idx);
strcpy(str,console_savedlines[recall]);
len = idx = strlen(console_savedlines[recall]);
console_eraseeol();
console_write(str,len);
nosave = 1;
break;
case CTRL('N'):
case VKEY_DOWN: /* Recall next line */
t = recall;
t++;
if (t == MAXSAVELINES) t = 0;
if (console_savedlines[t] == NULL) break;
recall = t;
console_backspace(idx);
strcpy(str,console_savedlines[recall]);
len = idx = strlen(console_savedlines[recall]);
console_eraseeol();
console_write(str,len);
nosave = 1;
break;
case VKEY_F1:
case VKEY_F2:
case VKEY_F3:
case VKEY_F4:
case VKEY_F5:
case VKEY_F6:
case VKEY_F7:
case VKEY_F8:
case VKEY_F9:
case VKEY_F10:
case VKEY_F11:
case VKEY_F12:
sprintf(env,"F%d",ch-VKEY_F1+1);
x = env_getenv(env);
if (x) {
console_backspace(idx);
strcpy(str,x);
idx = len = strlen(str);
console_eraseeol();
console_write(str,len);
console_crlf();
reading = 0;
nosave = 1;
}
/*
* If F12 is undefined, it means "repeat last command"
*/
if (ch == VKEY_F12) {
t = recall;
t--;
if (t < 0) t = MAXSAVELINES-1;
if (console_savedlines[t] == NULL) break;
recall = t;
console_backspace(idx);
strcpy(str,console_savedlines[recall]);
len = idx = strlen(console_savedlines[recall]);
console_eraseeol();
console_write(str,len);
console_crlf();
reading = 0;
nosave = 1;
}
break;
default: /* insert character */
if (ch >= ' ') {
if (idx < (maxlen-1)) {
nosave = 0;
for (t = len; t > idx; t--) {
str[t] = str[t-1];
}
str[idx] = ch;
len++;
if (len != idx) {
if(g_enable_printf) /*------skyworth add*/
{
console_write(&str[idx],len-idx);
}
console_backspace(len-idx-1);
}
idx++;
}
}
break;
}
}
POLL();
console_inreadline--;
str[len] = 0;
if ((len != 0) && !nosave) {
if (console_savedlines[console_nextsave]) {
KFREE(console_savedlines[console_nextsave]);
}
console_savedlines[console_nextsave] = strdup(str);
console_nextsave++;
if (console_nextsave == MAXSAVELINES) console_nextsave = 0;
}
return len;
}
int console_readkey(void)
{
unsigned char ch;
int num;
GETCHAR(ch);
switch (ch) {
case VKEY_ESC:
GETCHAR(ch);
switch (ch) {
case 'O':
GETCHAR(ch);
switch (ch) {
case 'P':
return VKEY_F1;
case 'Q':
return VKEY_F2;
case 'R':
return VKEY_F3;
case 'S':
return VKEY_F4;
}
return (int)ch;
case '[':
GETCHAR(ch);
if ((ch >= '0') && (ch <= '9')) {
console_readnum(&num,&ch);
if (ch == '~') {
switch (num) {
case 2:
return VKEY_HOME;
case 3:
return VKEY_PGUP;
case 5:
if (console_mode == XTERM) return VKEY_PGUP;
return VKEY_END;
case 6:
if (console_mode == XTERM) return VKEY_PGDN;
return VKEY_PGDN;
case 11:
return VKEY_F1;
case 12:
return VKEY_F2;
case 13:
return VKEY_F3;
case 14:
return VKEY_F4;
case 15:
return VKEY_F5;
case 17:
return VKEY_F6;
case 18:
return VKEY_F7;
case 19:
return VKEY_F8;
case 20:
return VKEY_F9;
case 21:
return VKEY_F10;
case 23:
return VKEY_F11;
case 24:
return VKEY_F12;
}
return (int)ch;
}
}
else {
switch (ch) {
case 'A':
return VKEY_UP;
case 'B':
return VKEY_DOWN;
case 'C':
return VKEY_RIGHT;
case 'D':
return VKEY_LEFT;
case 'F':
return VKEY_HOME;
case 'H':
return VKEY_END;
default:
return (int) ch;
}
}
default:
return (int)ch;
}
default:
return (int) ch;
}
}
static void console_readnum(int *num,unsigned char *ch)
{
int total = 0;
for (;;) {
total = (total * 10) + (*ch - '0');
while (console_read(ch,1) != 1) { POLL(); }
if (!((*ch >= '0') && (*ch <= '9'))) break;
}
*num = total;
}
static void console_backspace(int n)
{
int t;
for (t = 0; t < n; t++) console_write("\b",1);
}
static void console_whiteout(int n)
{
int t;
for (t = 0; t < n; t++) console_write(" ",1);
for (t = 0; t < n; t++) console_write("\b",1);
}
static void console_eraseeol(void)
{
console_write("\033[K",3);
}
static void console_crlf(void)
{
console_write("\r\n",2);
}
上述代码我就不做过多解释了,一看就会明白,这里主要提一下console_readkey(), 为啥返回的是Int型而不是char型呢,这主要涉及到转义序列, 普通字符直接转化为int返回了
len为输入的总的字符数,idx为当前光标所在的位置,console_write()用于回显和光标的移动