From c15f40c3e7bfeae1b616c53a574308c2df707b3d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 15 May 2009 03:27:53 +0200 Subject: [PATCH] lineedit: use read_key to recognize ESC sequence. This fixes several vi mode bugs and prepares for further fixes. function old new delta read_line_input 3287 5511 +2224 remember_in_history - 499 +499 lineedit_read_key - 70 +70 read_key 321 332 +11 input_tab 2823 - -2823 ------------------------------------------------------------------------------ (add/remove: 2/1 grow/shrink: 2/0 up/down: 2804/-2823) Total: -19 bytes Signed-off-by: Denys Vlasenko --- include/libbb.h | 24 ++-- libbb/lineedit.c | 319 ++++++++++++++++++++++------------------------- libbb/read_key.c | 1 + 3 files changed, 163 insertions(+), 181 deletions(-) diff --git a/include/libbb.h b/include/libbb.h index 3a94a0070..128aa9207 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1198,23 +1198,23 @@ unsigned long long bb_makedev(unsigned int major, unsigned int minor) FAST_FUNC; #if ENABLE_FEATURE_EDITING /* It's NOT just ENABLEd or disabled. It's a number: */ -#ifdef CONFIG_FEATURE_EDITING_HISTORY -# define MAX_HISTORY (CONFIG_FEATURE_EDITING_HISTORY + 0) -#else -# define MAX_HISTORY 0 -#endif +# ifdef CONFIG_FEATURE_EDITING_HISTORY +# define MAX_HISTORY (CONFIG_FEATURE_EDITING_HISTORY + 0) +# else +# define MAX_HISTORY 0 +# endif typedef struct line_input_t { int flags; const char *path_lookup; -#if MAX_HISTORY +# if MAX_HISTORY int cnt_history; int cur_history; -#if ENABLE_FEATURE_EDITING_SAVEHISTORY +# if ENABLE_FEATURE_EDITING_SAVEHISTORY unsigned cnt_history_in_file; const char *hist_file; -#endif +# endif char *history[MAX_HISTORY + 1]; -#endif +# endif } line_input_t; enum { DO_HISTORY = 1 * (MAX_HISTORY > 0), @@ -1241,12 +1241,12 @@ int read_line_input(const char* prompt, char* command, int maxsize) FAST_FUNC; #ifndef COMM_LEN -#ifdef TASK_COMM_LEN +# ifdef TASK_COMM_LEN enum { COMM_LEN = TASK_COMM_LEN }; -#else +# else /* synchronize with sizeof(task_struct.comm) in /usr/include/linux/sched.h */ enum { COMM_LEN = 16 }; -#endif +# endif #endif typedef struct procps_status_t { DIR *dir; diff --git a/libbb/lineedit.c b/libbb/lineedit.c index e0ab73250..ccf3e0dc8 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -1430,6 +1430,22 @@ static void win_changed(int nsig) signal(SIGWINCH, win_changed); /* rearm ourself */ } +static int lineedit_read_key(smalluint *read_key_bufsize, char *read_key_buffer) +{ + int ic; + struct pollfd pfd; + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + do { + /* Wait for input. Can't just call read_key, it will return + * at once if stdin is in non-blocking mode. */ + safe_poll(&pfd, 1, -1); + /* note: read_key sets errno to 0 on success: */ + ic = read_key(STDIN_FILENO, read_key_bufsize, read_key_buffer); + } while (errno == EAGAIN); + return ic; +} + /* * The emacs and vi modes share much of the code in the big * command loop. Commands entered when in vi's command mode (aka @@ -1438,7 +1454,7 @@ static void win_changed(int nsig) * big switch a bit, but keeps all the code in one place. */ -#define vbit 0x100 +#define VI_CMDMODE_BIT 0x100 /* leave out the "vi-mode"-only case labels if vi editing isn't * configured. */ @@ -1459,15 +1475,15 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li #if ENABLE_FEATURE_TAB_COMPLETION smallint lastWasTab = FALSE; #endif - unsigned ic; - unsigned char c; + int ic; smallint break_out = 0; #if ENABLE_FEATURE_EDITING_VI smallint vi_cmdmode = 0; - smalluint prevc; #endif struct termios initial_settings; struct termios new_settings; + smalluint read_key_bufsize; + char read_key_buffer[KEYCODE_BUFFER_SIZE]; INIT_S(); @@ -1545,42 +1561,40 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li while (1) { fflush(NULL); - - if (nonblock_safe_read(STDIN_FILENO, &c, 1) < 1) { - /* if we can't read input then exit */ - goto prepare_to_die; - } - - ic = c; + ic = lineedit_read_key(&read_key_bufsize, read_key_buffer); #if ENABLE_FEATURE_EDITING_VI newdelflag = 1; - if (vi_cmdmode) - ic |= vbit; + if (vi_cmdmode) { + /* btw, since KEYCODE_xxx are all < 0, this doesn't + * change ic if it contains one of them: */ + ic |= VI_CMDMODE_BIT; + } #endif + switch (ic) { case '\n': case '\r': - vi_case('\n'|vbit:) - vi_case('\r'|vbit:) + vi_case('\n'|VI_CMDMODE_BIT:) + vi_case('\r'|VI_CMDMODE_BIT:) /* Enter */ goto_new_line(); break_out = 1; break; case CTRL('A'): - vi_case('0'|vbit:) + vi_case('0'|VI_CMDMODE_BIT:) /* Control-a -- Beginning of line */ input_backward(cursor); break; case CTRL('B'): - vi_case('h'|vbit:) - vi_case('\b'|vbit:) - vi_case('\x7f'|vbit:) /* DEL */ + vi_case('h'|VI_CMDMODE_BIT:) + vi_case('\b'|VI_CMDMODE_BIT:) + vi_case('\x7f'|VI_CMDMODE_BIT:) /* DEL */ /* Control-b -- Move back one character */ input_backward(1); break; case CTRL('C'): - vi_case(CTRL('C')|vbit:) + vi_case(CTRL('C')|VI_CMDMODE_BIT:) /* Control-c -- stop gathering input */ goto_new_line(); command_len = 0; @@ -1598,31 +1612,27 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li } input_delete(0); break; - case CTRL('E'): - vi_case('$'|vbit:) + vi_case('$'|VI_CMDMODE_BIT:) /* Control-e -- End of line */ input_end(); break; case CTRL('F'): - vi_case('l'|vbit:) - vi_case(' '|vbit:) + vi_case('l'|VI_CMDMODE_BIT:) + vi_case(' '|VI_CMDMODE_BIT:) /* Control-f -- Move forward one character */ input_forward(); break; - case '\b': case '\x7f': /* DEL */ /* Control-h and DEL */ input_backspace(); break; - #if ENABLE_FEATURE_TAB_COMPLETION case '\t': input_tab(&lastWasTab); break; #endif - case CTRL('K'): /* Control-k -- clear to end of line */ command[cursor] = 0; @@ -1630,31 +1640,29 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li printf("\033[J"); break; case CTRL('L'): - vi_case(CTRL('L')|vbit:) + vi_case(CTRL('L')|VI_CMDMODE_BIT:) /* Control-l -- clear screen */ printf("\033[H"); redraw(0, command_len - cursor); break; - #if MAX_HISTORY > 0 case CTRL('N'): - vi_case(CTRL('N')|vbit:) - vi_case('j'|vbit:) + vi_case(CTRL('N')|VI_CMDMODE_BIT:) + vi_case('j'|VI_CMDMODE_BIT:) /* Control-n -- Get next command in history */ if (get_next_history()) goto rewrite_line; break; case CTRL('P'): - vi_case(CTRL('P')|vbit:) - vi_case('k'|vbit:) + vi_case(CTRL('P')|VI_CMDMODE_BIT:) + vi_case('k'|VI_CMDMODE_BIT:) /* Control-p -- Get previous command from history */ if (get_previous_history()) goto rewrite_line; break; #endif - case CTRL('U'): - vi_case(CTRL('U')|vbit:) + vi_case(CTRL('U')|VI_CMDMODE_BIT:) /* Control-U -- Clear line before cursor */ if (cursor) { overlapping_strcpy(command, command + cursor); @@ -1663,7 +1671,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li } break; case CTRL('W'): - vi_case(CTRL('W')|vbit:) + vi_case(CTRL('W')|VI_CMDMODE_BIT:) /* Control-W -- Remove the last word */ while (cursor > 0 && isspace(command[cursor-1])) input_backspace(); @@ -1672,75 +1680,80 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li break; #if ENABLE_FEATURE_EDITING_VI - case 'i'|vbit: + case 'i'|VI_CMDMODE_BIT: vi_cmdmode = 0; break; - case 'I'|vbit: + case 'I'|VI_CMDMODE_BIT: input_backward(cursor); vi_cmdmode = 0; break; - case 'a'|vbit: + case 'a'|VI_CMDMODE_BIT: input_forward(); vi_cmdmode = 0; break; - case 'A'|vbit: + case 'A'|VI_CMDMODE_BIT: input_end(); vi_cmdmode = 0; break; - case 'x'|vbit: + case 'x'|VI_CMDMODE_BIT: input_delete(1); break; - case 'X'|vbit: + case 'X'|VI_CMDMODE_BIT: if (cursor > 0) { input_backward(1); input_delete(1); } break; - case 'W'|vbit: + case 'W'|VI_CMDMODE_BIT: vi_Word_motion(command, 1); break; - case 'w'|vbit: + case 'w'|VI_CMDMODE_BIT: vi_word_motion(command, 1); break; - case 'E'|vbit: + case 'E'|VI_CMDMODE_BIT: vi_End_motion(command); break; - case 'e'|vbit: + case 'e'|VI_CMDMODE_BIT: vi_end_motion(command); break; - case 'B'|vbit: + case 'B'|VI_CMDMODE_BIT: vi_Back_motion(command); break; - case 'b'|vbit: + case 'b'|VI_CMDMODE_BIT: vi_back_motion(command); break; - case 'C'|vbit: + case 'C'|VI_CMDMODE_BIT: vi_cmdmode = 0; /* fall through */ - case 'D'|vbit: + case 'D'|VI_CMDMODE_BIT: goto clear_to_eol; - case 'c'|vbit: + case 'c'|VI_CMDMODE_BIT: vi_cmdmode = 0; /* fall through */ - case 'd'|vbit: { + case 'd'|VI_CMDMODE_BIT: { int nc, sc; + int prev_ic; + sc = cursor; - prevc = ic; - if (safe_read(STDIN_FILENO, &c, 1) < 1) + prev_ic = ic; + + ic = lineedit_read_key(&read_key_bufsize, read_key_buffer); + if (errno) /* error */ goto prepare_to_die; - if (c == (prevc & 0xff)) { + + if ((ic | VI_CMDMODE_BIT) == prev_ic) { /* "cc", "dd" */ input_backward(cursor); goto clear_to_eol; break; } - switch (c) { + switch (ic) { case 'w': case 'W': case 'e': case 'E': - switch (c) { + switch (ic) { case 'w': /* "dw", "cw" */ vi_word_motion(command, vi_cmdmode); break; @@ -1763,7 +1776,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li break; case 'b': /* "db", "cb" */ case 'B': /* implemented as B */ - if (c == 'b') + if (ic == 'b') vi_back_motion(command); else vi_Back_motion(command); @@ -1774,143 +1787,109 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li input_delete(1); break; case '$': /* "d$", "c$" */ - clear_to_eol: + clear_to_eol: while (cursor < command_len) input_delete(1); break; } break; } - case 'p'|vbit: + case 'p'|VI_CMDMODE_BIT: input_forward(); /* fallthrough */ - case 'P'|vbit: + case 'P'|VI_CMDMODE_BIT: put(); break; - case 'r'|vbit: - if (safe_read(STDIN_FILENO, &c, 1) < 1) + case 'r'|VI_CMDMODE_BIT: + ic = lineedit_read_key(&read_key_bufsize, read_key_buffer); + if (errno) /* error */ goto prepare_to_die; - if (c == 0) + if (ic < ' ' || ic > 255) { beep(); - else { - *(command + cursor) = c; - bb_putchar(c); + } else { + command[cursor] = ic; + bb_putchar(ic); bb_putchar('\b'); } break; + case '\x1b': /* ESC */ + if (state->flags & VI_MODE) { + /* insert mode --> command mode */ + vi_cmdmode = 1; + input_backward(1); + } + break; #endif /* FEATURE_COMMAND_EDITING_VI */ - case '\x1b': /* ESC */ - -#if ENABLE_FEATURE_EDITING_VI - if (state->flags & VI_MODE) { - /* ESC: insert mode --> command mode */ - vi_cmdmode = 1; - input_backward(1); - break; - } -#endif - /* escape sequence follows */ - if (safe_read(STDIN_FILENO, &c, 1) < 1) - goto prepare_to_die; - /* different vt100 emulations */ - if (c == '[' || c == 'O') { - vi_case('['|vbit:) - vi_case('O'|vbit:) - if (safe_read(STDIN_FILENO, &c, 1) < 1) - goto prepare_to_die; - } - if (c >= '1' && c <= '9') { - unsigned char dummy; - - if (safe_read(STDIN_FILENO, &dummy, 1) < 1) - goto prepare_to_die; - if (dummy != '~') - c = '\0'; - } - - switch (c) { -#if ENABLE_FEATURE_TAB_COMPLETION - case '\t': /* Alt-Tab */ - input_tab(&lastWasTab); - break; -#endif #if MAX_HISTORY > 0 - case 'A': - /* Up Arrow -- Get previous command from history */ - if (get_previous_history()) - goto rewrite_line; - beep(); + case KEYCODE_UP: + if (get_previous_history()) + goto rewrite_line; + beep(); + break; + case KEYCODE_DOWN: + if (!get_next_history()) break; - case 'B': - /* Down Arrow -- Get next command in history */ - if (!get_next_history()) - break; rewrite_line: - /* Rewrite the line with the selected history item */ - /* change command */ - command_len = strlen(strcpy(command, state->history[state->cur_history] ? : "")); - /* redraw and go to eol (bol, in vi */ - redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); - break; + /* Rewrite the line with the selected history item */ + /* change command */ + command_len = strlen(strcpy(command, state->history[state->cur_history] ? : "")); + /* redraw and go to eol (bol, in vi) */ + redraw(cmdedit_y, (state->flags & VI_MODE) ? 9999 : 0); + break; #endif - case 'C': - /* Right Arrow -- Move forward one character */ - input_forward(); - break; - case 'D': - /* Left Arrow -- Move back one character */ - input_backward(1); - break; - case '3': - /* Delete */ - input_delete(0); - break; - case '1': // vt100? linux vt? or what? - case '7': // vt100? linux vt? or what? - case 'H': /* xterm's */ - input_backward(cursor); - break; - case '4': // vt100? linux vt? or what? - case '8': // vt100? linux vt? or what? - case 'F': /* xterm's */ - input_end(); - break; - default: - c = '\0'; - beep(); - } + case KEYCODE_RIGHT: + input_forward(); + break; + case KEYCODE_LEFT: + input_backward(1); + break; + case KEYCODE_DELETE: + input_delete(0); + break; + case KEYCODE_HOME: + input_backward(cursor); + break; + case KEYCODE_END: + input_end(); break; - default: /* If it's regular input, do the normal thing */ - - /* Control-V -- force insert of next char */ - if (c == CTRL('V')) { - if (safe_read(STDIN_FILENO, &c, 1) < 1) - goto prepare_to_die; - if (c == 0) { - beep(); - break; - } + default: +// /* Control-V -- force insert of next char */ +// if (c == CTRL('V')) { +// if (safe_read(STDIN_FILENO, &c, 1) < 1) +// goto prepare_to_die; +// if (c == 0) { +// beep(); +// break; +// } +// } + if (ic < ' ' || ic > 255) { + /* If VI_CMDMODE_BIT is set, ic is >= 256 + * and command mode ignores unexpected chars. + * Otherwise, we are here if ic is a + * control char or an unhandled ESC sequence, + * which is also ignored. + */ + break; + } + if ((int)command_len >= (maxsize - 2)) { + /* Not enough space for the char and EOL */ + break; } -#if ENABLE_FEATURE_EDITING_VI - if (vi_cmdmode) /* Don't self-insert */ - break; -#endif - if ((int)command_len >= (maxsize - 2)) /* Need to leave space for enter */ - break; - command_len++; - if (cursor == (command_len - 1)) { /* Append if at the end of the line */ - command[cursor] = c; - command[cursor+1] = '\0'; + if (cursor == (command_len - 1)) { + /* We are at the end, append */ + command[cursor] = ic; + command[cursor + 1] = '\0'; cmdedit_set_out_char(' '); - } else { /* Insert otherwise */ + } else { + /* In the middle, insert */ int sc = cursor; memmove(command + sc + 1, command + sc, command_len - sc); - command[sc] = c; + command[sc] = ic; sc++; /* rewrite from cursor */ input_end(); @@ -1918,15 +1897,17 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li input_backward(cursor - sc); } break; - } - if (break_out) /* Enter is the command terminator, no more input. */ + } /* switch (input_key) */ + + if (break_out) break; #if ENABLE_FEATURE_TAB_COMPLETION - if (c != '\t') + ic &= ~VI_CMDMODE_BIT; + if (ic != '\t') lastWasTab = FALSE; #endif - } + } /* while (1) */ if (command_len > 0) remember_in_history(command); diff --git a/libbb/read_key.c b/libbb/read_key.c index 0f36d20b6..fd100b0ec 100644 --- a/libbb/read_key.c +++ b/libbb/read_key.c @@ -66,6 +66,7 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer) 0 }; + errno = 0; n = 0; if (nbuffered) n = *nbuffered;