less: reuse former vi's key reading code. Improve SIGWINCH handling.
authorDenis Vlasenko <vda.linux@googlemail.com>
Sat, 25 Oct 2008 23:27:29 +0000 (23:27 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sat, 25 Oct 2008 23:27:29 +0000 (23:27 -0000)
function                                             old     new   delta
less_main                                           2056    2097     +41
getch_nowait                                         248     273     +25
read_key                                             310     321     +11
static.esccmds                                        61      69      +8
count_lines                                           72      74      +2
less_gets                                            166     142     -24
less_getch                                           172      43    -129
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 6/5 up/down: 91/-170)           Total: -79 bytes
   text    data     bss     dec     hex filename

editors/vi.c
include/libbb.h
libbb/read_key.c
miscutils/less.c

index 0e7b729fed24263c85fc9e770421cd2838ee5888..9960f7486cf793f0abd418a0d3594841a6fbcb55 100644 (file)
@@ -193,7 +193,7 @@ struct globals {
 #if ENABLE_FEATURE_VI_CRASHME
        char readbuffer[128];
 #else
-       char readbuffer[8];
+       char readbuffer[KEYCODE_BUFFER_SIZE];
 #endif
 #define STATUS_BUFFER_LEN  200
        char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
index 93171096253c8f83502bf8c43a71820265c00262..2a8b183365fec3f66216b5a99555a5b27f4f0230 100644 (file)
@@ -950,6 +950,8 @@ enum {
        KEYCODE_FUN11    = -22,
        KEYCODE_FUN12    = -23,
 #endif
+       /* How long the longest ESC sequence we know? */
+       KEYCODE_BUFFER_SIZE = 4
 };
 int read_key(int fd, smalluint *nbuffered, char *buffer) FAST_FUNC;
 
index 598bd94f558671f226923ceadecf4c41dd8770a9..614e2f9e045e74a9cd566574d9c40df59725e9d4 100644 (file)
@@ -33,14 +33,16 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
                '[','B'        |0x80,KEYCODE_DOWN    ,
                '[','C'        |0x80,KEYCODE_RIGHT   ,
                '[','D'        |0x80,KEYCODE_LEFT    ,
-               '[','H'        |0x80,KEYCODE_HOME    ,
-               '[','F'        |0x80,KEYCODE_END     ,
-               '[','1','~'    |0x80,KEYCODE_HOME    ,
+               '[','H'        |0x80,KEYCODE_HOME    , /* xterm */
+               '[','F'        |0x80,KEYCODE_END     , /* xterm */
+               '[','1','~'    |0x80,KEYCODE_HOME    , /* vt100? linux vt? or what? */
                '[','2','~'    |0x80,KEYCODE_INSERT  ,
                '[','3','~'    |0x80,KEYCODE_DELETE  ,
-               '[','4','~'    |0x80,KEYCODE_END     ,
+               '[','4','~'    |0x80,KEYCODE_END     , /* vt100? linux vt? or what? */
                '[','5','~'    |0x80,KEYCODE_PAGEUP  ,
                '[','6','~'    |0x80,KEYCODE_PAGEDOWN,
+               '[','7','~'    |0x80,KEYCODE_HOME    , /* vt100? linux vt? or what? */
+               '[','8','~'    |0x80,KEYCODE_END     , /* vt100? linux vt? or what? */
 #if 0
                '[','1','1','~'|0x80,KEYCODE_FUN1    ,
                '[','1','2','~'|0x80,KEYCODE_FUN2    ,
@@ -58,7 +60,9 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
                0
        };
 
-       n = *nbuffered;
+       n = 0;
+       if (nbuffered)
+               n = *nbuffered;
        if (n == 0) {
                /* If no data, block waiting for input. If we read more
                 * than the minimal ESC sequence size, the "n=0" below
@@ -141,6 +145,7 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
         * by now. */
 
  ret:
-       *nbuffered = n;
+       if (nbuffered)
+               *nbuffered = n;
        return c;
 }
index f367b0ebcf27480d88af9f21944bddbc0d16c0f5..d9ada619a3b18c162b20c727d22b03ca814a6bdd 100644 (file)
 /* The escape code to clear to end of line */
 #define CLEAR_2_EOL "\033[K"
 
-/* These are the escape sequences corresponding to special keys */
 enum {
-       REAL_KEY_UP = 'A',
-       REAL_KEY_DOWN = 'B',
-       REAL_KEY_RIGHT = 'C',
-       REAL_KEY_LEFT = 'D',
-       REAL_PAGE_UP = '5',
-       REAL_PAGE_DOWN = '6',
-       REAL_KEY_HOME = '7', // vt100? linux vt? or what?
-       REAL_KEY_END = '8',
-       REAL_KEY_HOME_ALT = '1', // ESC [1~ (vt100? linux vt? or what?)
-       REAL_KEY_END_ALT = '4', // ESC [4~
-       REAL_KEY_HOME_XTERM = 'H',
-       REAL_KEY_END_XTERM = 'F',
-
-/* These are the special codes assigned by this program to the special keys */
-       KEY_UP = 20,
-       KEY_DOWN = 21,
-       KEY_RIGHT = 22,
-       KEY_LEFT = 23,
-       PAGE_UP = 24,
-       PAGE_DOWN = 25,
-       KEY_HOME = 26,
-       KEY_END = 27,
-
 /* Absolute max of lines eaten */
        MAXLINES = CONFIG_FEATURE_LESS_MAXLINES,
-
 /* This many "after the end" lines we will show (at max) */
        TILDES = 1,
 };
@@ -133,6 +108,8 @@ struct globals {
 #define max_displayed_line  (G.max_displayed_line)
 #define width               (G.width             )
 #define winch_counter       (G.winch_counter     )
+/* This one is 100% not cached by compiler on read access */
+#define WINCH_COUNTER (*(volatile unsigned *)&winch_counter)
 #define eof_error           (G.eof_error         )
 #define readpos             (G.readpos           )
 #define readeof             (G.readeof           )
@@ -824,9 +801,10 @@ static void reinitialize(void)
        buffer_fill_and_print();
 }
 
-static ssize_t getch_nowait(char* input, int sz)
+static ssize_t getch_nowait(void)
 {
-       ssize_t rd;
+       char input[KEYCODE_BUFFER_SIZE];
+       int rd;
        struct pollfd pfd[2];
 
        pfd[0].fd = STDIN_FILENO;
@@ -842,34 +820,39 @@ static ssize_t getch_nowait(char* input, int sz)
         * (switch fd into O_NONBLOCK'ed mode to avoid it)
         */
        rd = 1;
-       if (max_fline <= cur_fline + max_displayed_line
-        && eof_error > 0 /* did NOT reach eof yet */
+       /* Are we interested in stdin? */
+//TODO: reuse code for determining this
+       if (!(option_mask32 & FLAG_S)
+          ? !(max_fline > cur_fline + max_displayed_line)
+          : !(max_fline >= cur_fline
+              && max_lineno > LINENO(flines[cur_fline]) + max_displayed_line)
        ) {
-               /* We are interested in stdin */
-               rd = 0;
+               if (eof_error > 0) /* did NOT reach eof yet */
+                       rd = 0; /* yes, we are interested in stdin */
        }
-       /* position cursor if line input is done */
+       /* Position cursor if line input is done */
        if (less_gets_pos >= 0)
                move_cursor(max_displayed_line + 2, less_gets_pos + 1);
        fflush(stdout);
 #if ENABLE_FEATURE_LESS_WINCH
        while (1) {
                int r;
+               /* NB: SIGWINCH interrupts poll() */
                r = poll(pfd + rd, 2 - rd, -1);
-               if (/*r < 0 && errno == EINTR &&*/ winch_counter) {
-                       input[0] = '\\'; /* anything which has no defined function */
-                       return 1;
-               }
+               if (/*r < 0 && errno == EINTR &&*/ winch_counter)
+                       return '\\'; /* anything which has no defined function */
                if (r) break;
        }
 #else
        safe_poll(pfd + rd, 2 - rd, -1);
 #endif
 
-       input[0] = '\0';
-       rd = safe_read(kbd_fd, input, sz); /* NB: kbd_fd is in O_NONBLOCK mode */
-       if (rd < 0 && errno == EAGAIN) {
-               /* No keyboard input -> we have input on stdin! */
+       /* We have kbd_fd in O_NONBLOCK mode, read inside read_key()
+        * would not block even if there is no input available */
+       rd = read_key(kbd_fd, NULL, input);
+       if (rd == -1 && errno == EAGAIN) {
+               /* No keyboard input available. Since poll() did return,
+                * we should have input on stdin */
                read_lines();
                buffer_fill_and_print();
                goto again;
@@ -883,51 +866,29 @@ static ssize_t getch_nowait(char* input, int sz)
  * special return codes. Note that this function works best with raw input. */
 static int less_getch(int pos)
 {
-       unsigned char input[16];
-       unsigned i;
+       int i;
 
  again:
        less_gets_pos = pos;
-       memset(input, 0, sizeof(input));
-       getch_nowait((char *)input, sizeof(input));
+       i = getch_nowait();
        less_gets_pos = -1;
 
-       /* Detect escape sequences (i.e. arrow keys) and handle
-        * them accordingly */
-       if (input[0] == '\033' && input[1] == '[') {
-               i = input[2] - REAL_KEY_UP;
-               if (i < 4)
-                       return 20 + i;
-               i = input[2] - REAL_PAGE_UP;
-               if (i < 4)
-                       return 24 + i;
-               if (input[2] == REAL_KEY_HOME_XTERM)
-                       return KEY_HOME;
-               if (input[2] == REAL_KEY_HOME_ALT)
-                       return KEY_HOME;
-               if (input[2] == REAL_KEY_END_XTERM)
-                       return KEY_END;
-               if (input[2] == REAL_KEY_END_ALT)
-                       return KEY_END;
-               return 0;
-       }
-       /* Reject almost all control chars */
-       i = input[0];
-       if (i < ' ' && i != 0x0d && i != 8)
+       /* Discard Ctrl-something chars */
+       if (i >= 0 && i < ' ' && i != 0x0d && i != 8)
                goto again;
        return i;
 }
 
 static char* less_gets(int sz)
 {
-       char c;
+       int c;
        unsigned i = 0;
        char *result = xzalloc(1);
 
        while (1) {
                c = '\0';
                less_gets_pos = sz + i;
-               getch_nowait(&c, 1);
+               c = getch_nowait();
                if (c == 0x0d) {
                        result[i] = '\0';
                        less_gets_pos = -1;
@@ -939,7 +900,7 @@ static char* less_gets(int sz)
                        printf("\x8 \x8");
                        i--;
                }
-               if (c < ' ')
+               if (c < ' ') /* filters out KEYCODE_xxx too (<0) */
                        continue;
                if (i >= width - sz - 1)
                        continue; /* len limit */
@@ -1151,8 +1112,8 @@ static void number_process(int first_digit)
 {
        unsigned i;
        int num;
+       int keypress;
        char num_input[sizeof(int)*4]; /* more than enough */
-       char keypress;
 
        num_input[0] = first_digit;
 
@@ -1163,15 +1124,14 @@ static void number_process(int first_digit)
        /* Receive input until a letter is given */
        i = 1;
        while (i < sizeof(num_input)-1) {
-               num_input[i] = less_getch(i + 1);
-               if (!num_input[i] || !isdigit(num_input[i]))
+               keypress = less_getch(i + 1);
+               if ((unsigned)keypress > 255 || !isdigit(num_input[i]))
                        break;
-               bb_putchar(num_input[i]);
+               num_input[i] = keypress;
+               bb_putchar(keypress);
                i++;
        }
 
-       /* Take the final letter out of the digits string */
-       keypress = num_input[i];
        num_input[i] = '\0';
        num = bb_strtou(num_input, NULL, 10);
        /* on format error, num == -1 */
@@ -1182,10 +1142,10 @@ static void number_process(int first_digit)
 
        /* We now know the number and the letter entered, so we process them */
        switch (keypress) {
-       case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
+       case KEYCODE_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015':
                buffer_down(num);
                break;
-       case KEY_UP: case 'b': case 'w': case 'y': case 'u':
+       case KEYCODE_UP: case 'b': case 'w': case 'y': case 'u':
                buffer_up(num);
                break;
        case 'g': case '<': case 'G': case '>':
@@ -1413,16 +1373,16 @@ static void match_left_bracket(char bracket)
 static void keypress_process(int keypress)
 {
        switch (keypress) {
-       case KEY_DOWN: case 'e': case 'j': case 0x0d:
+       case KEYCODE_DOWN: case 'e': case 'j': case 0x0d:
                buffer_down(1);
                break;
-       case KEY_UP: case 'y': case 'k':
+       case KEYCODE_UP: case 'y': case 'k':
                buffer_up(1);
                break;
-       case PAGE_DOWN: case ' ': case 'z': case 'f':
+       case KEYCODE_PAGEDOWN: case ' ': case 'z': case 'f':
                buffer_down(max_displayed_line + 1);
                break;
-       case PAGE_UP: case 'w': case 'b':
+       case KEYCODE_PAGEUP: case 'w': case 'b':
                buffer_up(max_displayed_line + 1);
                break;
        case 'd':
@@ -1431,10 +1391,10 @@ static void keypress_process(int keypress)
        case 'u':
                buffer_up((max_displayed_line + 1) / 2);
                break;
-       case KEY_HOME: case 'g': case 'p': case '<': case '%':
+       case KEYCODE_HOME: case 'g': case 'p': case '<': case '%':
                buffer_line(0);
                break;
-       case KEY_END: case 'G': case '>':
+       case KEYCODE_END: case 'G': case '>':
                cur_fline = MAXLINES;
                read_lines();
                buffer_line(cur_fline);
@@ -1586,7 +1546,8 @@ int less_main(int argc, char **argv)
        reinitialize();
        while (1) {
 #if ENABLE_FEATURE_LESS_WINCH
-               if (winch_counter) {
+               while (WINCH_COUNTER) {
+ again:
                        winch_counter--;
                        get_terminal_width_height(kbd_fd, &width, &max_displayed_line);
                        /* 20: two tabstops + 4 */
@@ -1597,8 +1558,16 @@ int less_main(int argc, char **argv)
                        max_displayed_line -= 2;
                        free(buffer);
                        buffer = xmalloc((max_displayed_line+1) * sizeof(char *));
+                       /* Avoid re-wrap and/or redraw if we already know
+                        * we need to do it again. These ops are expensive */
+                       if (WINCH_COUNTER)
+                               goto again;
                        re_wrap();
+                       if (WINCH_COUNTER)
+                               goto again;
                        buffer_fill_and_print();
+                       /* This took some time. Loop back and check,
+                        * were there another SIGWINCH? */
                }
 #endif
                keypress = less_getch(-1); /* -1: do not position cursor */