vi: trivial size optimization -65 bytes
[oweals/busybox.git] / editors / vi.c
index 1f1d3ca39a89b930598cf7b099197e590fcdcde7..a01fa7c46328b1eb08728edc701445b242fabaea 100644 (file)
@@ -147,10 +147,10 @@ struct globals {
 #endif
 
        smallint editing;        // >0 while we are editing a file
-                                // [code audit says "can be 0 or 1 only"]
+                                // [code audit says "can be 0, 1 or 2 only"]
        smallint cmd_mode;       // 0=command  1=insert 2=replace
        int file_modified;       // buffer contents changed (counter, not flag!)
-       int last_file_modified; // = -1;
+       int last_file_modified;  // = -1;
        int fn_start;            // index of first cmd line file name
        int save_argc;           // how many file names on cmd line
        int cmdcnt;              // repetition count
@@ -209,11 +209,11 @@ struct globals {
        char *initial_cmds[3];  // currently 2 entries, NULL terminated
 #endif
        // Should be just enough to hold a key sequence,
-       // but CRASME mode uses it as generated command buffer too
+       // but CRASHME mode uses it as generated command buffer too
 #if ENABLE_FEATURE_VI_CRASHME
-       char readbuffer[128];
+        char readbuffer[128];
 #else
-       char readbuffer[32];
+        char readbuffer[8];
 #endif
 #define STATUS_BUFFER_LEN  200
        char status_buffer[STATUS_BUFFER_LEN]; // messages to the user
@@ -348,6 +348,7 @@ static void place_cursor(int, int, int);
 static void screen_erase(void);
 static void clear_to_eol(void);
 static void clear_to_eos(void);
+static void go_bottom_and_clear_to_eol(void);
 static void standout_start(void);      // send "start reverse video" sequence
 static void standout_end(void);        // send "end reverse video" sequence
 static void flash(int);                // flash the terminal screen
@@ -623,7 +624,7 @@ static void edit_file(char *fn)
                // These are commands that change text[].
                // Remember the input for the "." command
                if (!adding2q && ioq_start == NULL
-                && strchr(modifying_cmds, c)
+                && c != '\0' && strchr(modifying_cmds, c)
                ) {
                        start_new_cmd_q(c);
                }
@@ -645,8 +646,7 @@ static void edit_file(char *fn)
        }
        //-------------------------------------------------------------------
 
-       place_cursor(rows, 0, FALSE);   // go to bottom of screen
-       clear_to_eol();         // Erase to end of line
+       go_bottom_and_clear_to_eol();
        cookmode();
 #undef cur_line
 }
@@ -842,8 +842,7 @@ static void colon(char *buf)
        else if (strncmp(cmd, "!", 1) == 0) {   // run a cmd
                int retcode;
                // :!ls   run the <cmd>
-               place_cursor(rows - 1, 0, FALSE);       // go to Status line
-               clear_to_eol();                 // clear the line
+               go_bottom_and_clear_to_eol();
                cookmode();
                retcode = system(orig_buf + 1); // run the cmd
                if (retcode)
@@ -920,8 +919,7 @@ static void colon(char *buf)
                }
        } else if (strncasecmp(cmd, "features", i) == 0) {      // what features are available
                // print out values of all features
-               place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
-               clear_to_eol(); // clear the line
+               go_bottom_and_clear_to_eol();
                cookmode();
                show_help();
                rawmode();
@@ -931,8 +929,7 @@ static void colon(char *buf)
                        q = begin_line(dot);    // assume .,. for the range
                        r = end_line(dot);
                }
-               place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
-               clear_to_eol(); // clear the line
+               go_bottom_and_clear_to_eol();
                puts("\r");
                for (; q <= r; q++) {
                        int c_is_no_print;
@@ -1032,8 +1029,7 @@ static void colon(char *buf)
                // only blank is regarded as args delmiter. What about tab '\t' ?
                if (!args[0] || strcasecmp(args, "all") == 0) {
                        // print out values of all options
-                       place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
-                       clear_to_eol(); // clear the line
+                       go_bottom_and_clear_to_eol();
                        printf("----------------------------------------\r\n");
 #if ENABLE_FEATURE_VI_SETOPTS
                        if (!autoindent)
@@ -2009,9 +2005,9 @@ static void start_new_cmd_q(char c)
 {
        // get buffer for new cmd
        // if there is a current cmd count put it in the buffer first
-       if (cmdcnt > 0)
+       if (cmdcnt > 0) {
                lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
-       else { // just save char c onto queue
+       else { // just save char c onto queue
                last_modifying_cmd[0] = c;
                lmc_len = 1;
        }
@@ -2157,21 +2153,20 @@ static void winch_sig(int sig UNUSED_PARAM)
 //----- Come here when we get a continue signal -------------------
 static void cont_sig(int sig UNUSED_PARAM)
 {
-       rawmode();                      // terminal to "raw"
-       last_status_cksum = 0;  // force status update
-       redraw(TRUE);           // re-draw the screen
+       rawmode(); // terminal to "raw"
+       last_status_cksum = 0; // force status update
+       redraw(TRUE); // re-draw the screen
 
        signal(SIGTSTP, suspend_sig);
        signal(SIGCONT, SIG_DFL);
-       kill(my_pid, SIGCONT);
+       kill(my_pid, SIGCONT); // huh? why? we are already "continued"...
 }
 
 //----- Come here when we get a Suspend signal -------------------
 static void suspend_sig(int sig UNUSED_PARAM)
 {
-       place_cursor(rows - 1, 0, FALSE);       // go to bottom of screen
-       clear_to_eol();         // Erase to end of line
-       cookmode();                     // terminal to "cooked"
+       go_bottom_and_clear_to_eol();
+       cookmode(); // terminal to "cooked"
 
        signal(SIGCONT, cont_sig);
        signal(SIGTSTP, SIG_DFL);
@@ -2201,18 +2196,23 @@ static char readit(void)        // read (maybe cursor) key from stdin
 {
        char c;
        int n;
-       struct esc_cmds {
+
+       // Known escape sequences for cursor and function keys.
+       static const struct esc_cmds {
                const char seq[4];
                char val;
-       };
-
-       static const struct esc_cmds esccmds[] = {
+       } esccmds[] = {
                {"OA"  , VI_K_UP      },   // cursor key Up
                {"OB"  , VI_K_DOWN    },   // cursor key Down
                {"OC"  , VI_K_RIGHT   },   // Cursor Key Right
                {"OD"  , VI_K_LEFT    },   // cursor key Left
                {"OH"  , VI_K_HOME    },   // Cursor Key Home
                {"OF"  , VI_K_END     },   // Cursor Key End
+               {"OP"  , VI_K_FUN1    },   // Function Key F1
+               {"OQ"  , VI_K_FUN2    },   // Function Key F2
+               {"OR"  , VI_K_FUN3    },   // Function Key F3
+               {"OS"  , VI_K_FUN4    },   // Function Key F4
+
                {"[A"  , VI_K_UP      },   // cursor key Up
                {"[B"  , VI_K_DOWN    },   // cursor key Down
                {"[C"  , VI_K_RIGHT   },   // Cursor Key Right
@@ -2225,10 +2225,6 @@ static char readit(void) // read (maybe cursor) key from stdin
                {"[4~" , VI_K_END     },   // Cursor Key End
                {"[5~" , VI_K_PAGEUP  },   // Cursor Key Page Up
                {"[6~" , VI_K_PAGEDOWN},   // Cursor Key Page Down
-               {"OP"  , VI_K_FUN1    },   // Function Key F1
-               {"OQ"  , VI_K_FUN2    },   // Function Key F2
-               {"OR"  , VI_K_FUN3    },   // Function Key F3
-               {"OS"  , VI_K_FUN4    },   // Function Key F4
                // careful: these have no terminating NUL!
                {"[11~", VI_K_FUN1    },   // Function Key F1
                {"[12~", VI_K_FUN2    },   // Function Key F2
@@ -2243,65 +2239,76 @@ static char readit(void)        // read (maybe cursor) key from stdin
                {"[23~", VI_K_FUN11   },   // Function Key F11
                {"[24~", VI_K_FUN12   },   // Function Key F12
        };
-       enum { ESCCMDS_COUNT = ARRAY_SIZE(esccmds) };
 
        fflush(stdout);
+
        n = chars_to_parse;
-       // get input from User- are there already input chars in Q?
-       if (n <= 0) {
-               // the Q is empty, wait for a typed char
-               n = safe_read(STDIN_FILENO, readbuffer, sizeof(readbuffer));
-               if (n < 0) {
-                       if (errno == EBADF || errno == EFAULT || errno == EINVAL
-                        || errno == EIO)
-                               editing = 0; // want to exit
-                       errno = 0;
-               }
-               if (n <= 0)
-                       return 0;       // error
-               if (readbuffer[0] == 27) {
-                       // This is an ESC char. Is this Esc sequence?
-                       // Could be bare Esc key. See if there are any
-                       // more chars to read after the ESC. This would
-                       // be a Function or Cursor Key sequence.
-                       struct pollfd pfd[1];
-                       pfd[0].fd = 0;
-                       pfd[0].events = POLLIN;
-                       // keep reading while there are input chars, and room in buffer
-                       // for a complete ESC sequence (assuming 8 chars is enough)
-                       while ((safe_poll(pfd, 1, 0) > 0)
-                        && ((size_t)n <= (sizeof(readbuffer) - 8))
-                       ) {
-                               // read the rest of the ESC string
-                               int r = safe_read(STDIN_FILENO, readbuffer + n, sizeof(readbuffer) - n);
-                               if (r > 0)
-                                       n += r;
-                       }
+       if (n == 0) {
+               // If no data, block waiting for input.
+               n = safe_read(0, readbuffer, 1);
+               if (n <= 0) {
+ error:
+                       go_bottom_and_clear_to_eol();
+                       cookmode(); // terminal to "cooked"
+                       bb_error_msg_and_die("can't read user input");
                }
-               chars_to_parse = n;
        }
+
+       // Grab character to return from buffer
        c = readbuffer[0];
-       if (c == 27 && n > 1) {
-               // Maybe cursor or function key?
+       // Returning NUL from this routine would be bad.
+       if (c == '\0')
+               c = ' ';
+       n--;
+       if (n) memmove(readbuffer, readbuffer + 1, n);
+
+       // If it's an escape sequence, loop through known matches.
+       if (c == 27) {
                const struct esc_cmds *eindex;
 
-               for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) {
+               for (eindex = esccmds; eindex < esccmds + ARRAY_SIZE(esccmds); eindex++) {
+                       // n - position in seq to read
+                       int i = 0; // position in seq to compare
                        int cnt = strnlen(eindex->seq, 4);
-                       if (n <= cnt)
-                               continue;
-                       if (strncmp(eindex->seq, readbuffer + 1, cnt) != 0)
-                               continue;
-                       c = eindex->val; // magic char value
-                       n = cnt + 1; // squeeze out the ESC sequence
-                       goto found;
-               }
-               // defined ESC sequence not found
-       }
-       n = 1;
- found:
-       // remove key sequence from Q
-       chars_to_parse -= n;
-       memmove(readbuffer, readbuffer + n, sizeof(readbuffer) - n);
+
+                       // Loop through chars in this sequence.
+                       for (;;) {
+                               // We've matched this escape sequence up to [i-1]
+                               if (n <= i) {
+                                       // Need more chars, read another one if it wouldn't block.
+                                       // (Note that escape sequences come in as a unit,
+                                       // so if we would block it's not really an escape sequence.)
+                                       struct pollfd pfd;
+                                       pfd.fd = 0;
+                                       pfd.events = POLLIN;
+                                       // Rob needed 300ms timeout on qemu
+                                       if (safe_poll(&pfd, 1, /*timeout:*/ 300)) {
+                                               if (safe_read(0, readbuffer + n, 1) <= 0)
+                                                       goto error;
+                                               n++;
+                                       } else {
+                                               // No more data!
+                                               // Array is sorted from shortest to longest,
+                                               // we can't match anything later in array,
+                                               // break out of both loops.
+                                               goto loop_out;
+                                       }
+                               }
+                               if (readbuffer[i] != eindex->seq[i])
+                                       break; // try next seq
+                               if (++i == cnt) { // entire seq matched
+                                       c = eindex->val;
+                                       n = 0;
+                                       goto loop_out;
+                               }
+                       }
+               }
+               // We did not find matching sequence, it was a bare ESC.
+               // We possibly read and stored more input in readbuffer by now.
+       }
+loop_out:
+
+       chars_to_parse = n;
        return c;
 }
 
@@ -2354,8 +2361,7 @@ static char *get_input_line(const char *prompt)
 
        strcpy(buf, prompt);
        last_status_cksum = 0;  // force status update
-       place_cursor(rows - 1, 0, FALSE);       // go to Status line, bottom of screen
-       clear_to_eol();         // clear the line
+       go_bottom_and_clear_to_eol();
        write1(prompt);      // write out the :, /, or ? prompt
 
        i = strlen(buf);
@@ -2492,6 +2498,14 @@ static int file_write(char *fn, char *first, char *last)
 static void place_cursor(int row, int col, int optimize)
 {
        char cm1[sizeof(CMrc) + sizeof(int)*3 * 2];
+#if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
+       enum {
+               SZ_UP = sizeof(CMup),
+               SZ_DN = sizeof(CMdown),
+               SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
+       };
+       char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
+#endif
        char *cm;
 
        if (row < 0) row = 0;
@@ -2505,12 +2519,6 @@ static void place_cursor(int row, int col, int optimize)
 
 #if ENABLE_FEATURE_VI_OPTIMIZE_CURSOR
        if (optimize && col < 16) {
-               enum {
-                       SZ_UP = sizeof(CMup),
-                       SZ_DN = sizeof(CMdown),
-                       SEQ_SIZE = SZ_UP > SZ_DN ? SZ_UP : SZ_DN,
-               };
-               char cm2[SEQ_SIZE * 5 + 32]; // bigger than worst case size
                char *screenp;
                int Rrow = last_row;
                int diff = Rrow - row;
@@ -2557,6 +2565,12 @@ static void clear_to_eol(void)
        write1(Ceol);   // Erase from cursor to end of line
 }
 
+static void go_bottom_and_clear_to_eol(void)
+{
+       place_cursor(rows - 1, 0, FALSE); // go to bottom of screen
+       clear_to_eol(); // erase to end of line
+}
+
 //----- Erase from cursor to end of screen -----------------------
 static void clear_to_eos(void)
 {
@@ -2628,9 +2642,8 @@ static void show_status_line(void)
        }
        if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
                last_status_cksum = cksum;              // remember if we have seen this line
-               place_cursor(rows - 1, 0, FALSE);       // put cursor on status line
+               go_bottom_and_clear_to_eol();
                write1(status_buffer);
-               clear_to_eol();
                if (have_status_msg) {
                        if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
                                        (columns - 1) ) {