line editing: add an option to emit ESC [ 6 n and use results
authorDenys Vlasenko <vda.linux@googlemail.com>
Sun, 17 May 2009 14:44:54 +0000 (16:44 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sun, 17 May 2009 14:44:54 +0000 (16:44 +0200)
This makes line editing able to recognize case when
cursor was not at the beginning of the line. It may also
be adapted later to find out display size (serial line users
would love it).

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
editors/vi.c
include/libbb.h
libbb/Config.in
libbb/lineedit.c
libbb/read_key.c
miscutils/less.c

index ccc53fb58a65fe1581c8e0371c4cdda6ca342647..ee5b5d98ac4b50ffee32fb8593307cf8ccad7f2f 100644 (file)
@@ -151,7 +151,6 @@ struct globals {
        char erase_char;         // the users erase character
        char last_input_char;    // last char read from user
 
-       smalluint chars_to_parse;
 #if ENABLE_FEATURE_VI_DOT_CMD
        smallint adding2q;       // are we currently adding user input to q
        int lmc_len;             // length of last_modifying_cmd
@@ -235,7 +234,6 @@ struct globals {
 #define last_forward_char       (G.last_forward_char  )
 #define erase_char              (G.erase_char         )
 #define last_input_char         (G.last_input_char    )
-#define chars_to_parse          (G.chars_to_parse     )
 #if ENABLE_FEATURE_VI_READONLY
 #define readonly_mode           (G.readonly_mode      )
 #else
@@ -620,7 +618,7 @@ static void edit_file(char *fn)
                // poll to see if there is input already waiting. if we are
                // not able to display output fast enough to keep up, skip
                // the display update until we catch up with input.
-               if (!chars_to_parse && mysleep(0) == 0) {
+               if (!readbuffer[0] && mysleep(0) == 0) {
                        // no input pending - so update output
                        refresh(FALSE);
                        show_status_line();
@@ -2206,7 +2204,7 @@ static int readit(void) // read (maybe cursor) key from stdin
        int c;
 
        fflush(stdout);
-       c = read_key(STDIN_FILENO, &chars_to_parse, readbuffer);
+       c = read_key(STDIN_FILENO, readbuffer);
        if (c == -1) { // EOF/error
                go_bottom_and_clear_to_eol();
                cookmode(); // terminal to "cooked"
@@ -3851,10 +3849,11 @@ static void crash_dummy()
        cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
 
        // is there already a command running?
-       if (chars_to_parse > 0)
+       if (readbuffer[0] > 0)
                goto cd1;
  cd0:
-       startrbi = rbi = 0;
+       readbuffer[0] = 'X';
+       startrbi = rbi = 1;
        sleeptime = 0;          // how long to pause between commands
        memset(readbuffer, '\0', sizeof(readbuffer));
        // generate a command by percentages
@@ -3928,7 +3927,7 @@ static void crash_dummy()
                }
                strcat(readbuffer, "\033");
        }
-       chars_to_parse = strlen(readbuffer);
+       readbuffer[0] = strlen(readbuffer + 1);
  cd1:
        totalcmds++;
        if (sleeptime > 0)
index bae7efb00bd4eb580a7ff24c744e4015907962a1..788140d14a6ec339198ab6aa1eea0f3ddc874571 100644 (file)
@@ -962,16 +962,23 @@ enum {
        KEYCODE_FUN11    = -22,
        KEYCODE_FUN12    = -23,
 #endif
-       /* How long the longest ESC sequence we know? */
-       KEYCODE_BUFFER_SIZE = 4
+       KEYCODE_CURSOR_POS = -0x100,
+       /* How long is the longest ESC sequence we know?
+        * We want it big enough to be able to contain
+        * cursor position sequence "ESC [ 9999 ; 9999 R"
+        */
+       KEYCODE_BUFFER_SIZE = 16
 };
 /* Note: fd may be in blocking or non-blocking mode, both make sense.
  * For one, less uses non-blocking mode.
  * Only the first read syscall inside read_key may block indefinitely
  * (unless fd is in non-blocking mode),
  * subsequent reads will time out after a few milliseconds.
+ * Return of -1 means EOF or error (errno == 0 on EOF).
+ * buffer[0] is used as a counter of buffered chars and must be 0
+ * on first call.
  */
-int read_key(int fd, smalluint *nbuffered, char *buffer) FAST_FUNC;
+int64_t read_key(int fd, char *buffer) FAST_FUNC;
 
 
 /* Networking */
index f5b804ff8508232e0fa81e41eee2796cd6bb4736..7ced387a9b46c85823b9bdd6b7f8bb579bb107bf 100644 (file)
@@ -102,6 +102,18 @@ config FEATURE_EDITING_FANCY_PROMPT
          Setting this option allows for prompts to use things like \w and
          \$ and escape codes.
 
+config FEATURE_EDITING_ASK_TERMINAL
+       bool "Query cursor position from terminal"
+       default n
+       depends on FEATURE_EDITING
+       help
+         Allow usage of "ESC [ 6 n" sequence. Terminal answers back with
+         current cursor position. This information is used to make line
+         editing more robust in some cases.
+         If you are not sure whether your terminals respond to this code
+         correctly, or want to save on code size (about 300 bytes),
+         then do not turn this option on.
+
 config FEATURE_VERBOSE_CP_MESSAGE
        bool "Give more precise messages when copy fails (cp, mv etc)"
        default n
index ccf3e0dc8c4753d02115c59e0b10edbd9592053c..e1404fb68634002fcc305127f47dc76f2f338279 100644 (file)
@@ -86,8 +86,8 @@ struct lineedit_statics {
        volatile unsigned cmdedit_termw; /* = 80; */ /* actual terminal width */
        sighandler_t previous_SIGWINCH_handler;
 
-       unsigned cmdedit_x;        /* real x terminal position */
-       unsigned cmdedit_y;        /* pseudoreal y terminal position */
+       unsigned cmdedit_x;        /* real x (col) terminal position */
+       unsigned cmdedit_y;        /* pseudoreal y (row) terminal position */
        unsigned cmdedit_prmt_len; /* length of prompt (without colors etc) */
 
        unsigned cursor;
@@ -299,6 +299,16 @@ static void input_backward(unsigned num)
 
 static void put_prompt(void)
 {
+#if ENABLE_FEATURE_EDITING_ASK_TERMINAL
+       /* Ask terminal where is cursor now.
+        * lineedit_read_key handles response and corrects
+        * our idea of current cursor position.
+        * Testcase: run "echo -n long_line_long_line_long_line",
+        * then type in a long, wrapping command and try to
+        * delete it using backspace key.
+        */
+       out1str("\033" "[6n");
+#endif
        out1str(cmdedit_prompt);
        cursor = 0;
        {
@@ -1430,18 +1440,33 @@ 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)
+static int lineedit_read_key(char *read_key_buffer)
 {
-       int ic;
+       int64_t ic;
        struct pollfd pfd;
+
        pfd.fd = STDIN_FILENO;
        pfd.events = POLLIN;
        do {
+ poll_again:
                /* 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);
+               ic = read_key(STDIN_FILENO, read_key_buffer);
+               if (ENABLE_FEATURE_EDITING_ASK_TERMINAL
+                && (int32_t)ic == KEYCODE_CURSOR_POS
+               ) {
+                       int col = ((ic >> 32) & 0x7fff) - 1;
+                       if (col > 0) {
+                               cmdedit_x += col;
+                               while (cmdedit_x >= cmdedit_termw) {
+                                       cmdedit_x -= cmdedit_termw;
+                                       cmdedit_y++;
+                               }
+                       }
+                       goto poll_again;
+               }
        } while (errno == EAGAIN);
        return ic;
 }
@@ -1482,7 +1507,6 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
 #endif
        struct termios initial_settings;
        struct termios new_settings;
-       smalluint read_key_bufsize;
        char read_key_buffer[KEYCODE_BUFFER_SIZE];
 
        INIT_S();
@@ -1561,7 +1585,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
 
        while (1) {
                fflush(NULL);
-               ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
+               ic = lineedit_read_key(read_key_buffer);
 
 #if ENABLE_FEATURE_EDITING_VI
                newdelflag = 1;
@@ -1738,7 +1762,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
                        sc = cursor;
                        prev_ic = ic;
 
-                       ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
+                       ic = lineedit_read_key(read_key_buffer);
                        if (errno) /* error */
                                goto prepare_to_die;
 
@@ -1801,7 +1825,7 @@ int FAST_FUNC read_line_input(const char *prompt, char *command, int maxsize, li
                        put();
                        break;
                case 'r'|VI_CMDMODE_BIT:
-                       ic = lineedit_read_key(&read_key_bufsize, read_key_buffer);
+                       ic = lineedit_read_key(read_key_buffer);
                        if (errno) /* error */
                                goto prepare_to_die;
                        if (ic < ' ' || ic > 255) {
index fd100b0ec2efe7b0b5c692957cf6ce451316318e..3771045d21c937df297b5e623bab3b41bcac40df 100644 (file)
@@ -9,7 +9,7 @@
  */
 #include "libbb.h"
 
-int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
+int64_t FAST_FUNC read_key(int fd, char *buffer)
 {
        struct pollfd pfd;
        const char *seq;
@@ -67,9 +67,7 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
        };
 
        errno = 0;
-       n = 0;
-       if (nbuffered)
-               n = *nbuffered;
+       n = (unsigned char) *buffer++;
        if (n == 0) {
                /* If no data, block waiting for input. If we read more
                 * than the minimal ESC sequence size, the "n=0" below
@@ -148,11 +146,54 @@ int FAST_FUNC read_key(int fd, smalluint *nbuffered, char *buffer)
                }
        }
        /* We did not find matching sequence, it was a bare ESC.
-        * We possibly read and stored more input in buffer[]
-        * by now. */
+        * We possibly read and stored more input in buffer[] by now. */
+
+       /* Try to decipher "ESC [ NNN ; NNN R" sequence */
+       if (ENABLE_FEATURE_EDITING_ASK_TERMINAL
+        && n != 0
+        && buffer[0] == '['
+       ) {
+               char *end;
+               unsigned long row, col;
+
+               while (n < KEYCODE_BUFFER_SIZE-1) { /* 1 for cnt */
+                       if (safe_poll(&pfd, 1, 50) == 0) {
+                               /* No more data! */
+                               break;
+                       }
+                       errno = 0;
+                       if (safe_read(fd, buffer + n, 1) <= 0) {
+                               /* If EAGAIN, then fd is O_NONBLOCK and poll lied:
+                                * in fact, there is no data. */
+                               if (errno != EAGAIN)
+                                       c = -1; /* otherwise it's EOF/error */
+                               goto ret;
+                       }
+                       if (buffer[n++] == 'R')
+                               goto got_R;
+               }
+               goto ret;
+ got_R:
+               if (!isdigit(buffer[1]))
+                       goto ret;
+               row = strtoul(buffer + 1, &end, 10);
+               if (*end != ';' || !isdigit(end[1]))
+                       goto ret;
+               col = strtoul(end + 1, &end, 10);
+               if (*end != 'R')
+                       goto ret;
+               if (row < 1 || col < 1 || (row | col) > 0x7fff)
+                       goto ret;
+
+               buffer[-1] = 0;
+
+               /* Pack into "1 <row15bits> <col16bits>" 32-bit sequence */
+               c = (((-1 << 15) | row) << 16) | col;
+               /* Return it in high-order word */
+               return ((int64_t) c << 32) | (uint32_t)KEYCODE_CURSOR_POS;
+       }
 
  ret:
-       if (nbuffered)
-               *nbuffered = n;
+       buffer[-1] = n;
        return c;
 }
index 702c4a891450652cc850aec7234f97d6766c6dbc..bd855066fbd2b6ee5b7d73cfd7c7782a02787950 100644 (file)
@@ -96,7 +96,6 @@ struct globals {
        smallint pattern_valid;
 #endif
        smallint terminated;
-       smalluint kbd_input_size;
        struct termios term_orig, term_less;
        char kbd_input[KEYCODE_BUFFER_SIZE];
 };
@@ -135,7 +134,6 @@ struct globals {
 #define terminated          (G.terminated        )
 #define term_orig           (G.term_orig         )
 #define term_less           (G.term_less         )
-#define kbd_input_size      (G.kbd_input_size    )
 #define kbd_input           (G.kbd_input         )
 #define INIT_G() do { \
        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
@@ -806,7 +804,7 @@ static void reinitialize(void)
        buffer_fill_and_print();
 }
 
-static ssize_t getch_nowait(void)
+static int getch_nowait(void)
 {
        int rd;
        struct pollfd pfd[2];
@@ -839,7 +837,7 @@ static ssize_t getch_nowait(void)
                move_cursor(max_displayed_line + 2, less_gets_pos + 1);
        fflush(stdout);
 
-       if (kbd_input_size == 0) {
+       if (kbd_input[0] == 0) { /* if nothing is buffered */
 #if ENABLE_FEATURE_LESS_WINCH
                while (1) {
                        int r;
@@ -856,7 +854,7 @@ static ssize_t getch_nowait(void)
 
        /* 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, &kbd_input_size, kbd_input);
+       rd = read_key(kbd_fd, kbd_input);
        if (rd == -1) {
                if (errno == EAGAIN) {
                        /* No keyboard input available. Since poll() did return,
@@ -872,9 +870,9 @@ static ssize_t getch_nowait(void)
        return rd;
 }
 
-/* Grab a character from input without requiring the return key. If the
- * character is ASCII \033, get more characters and assign certain sequences
- * special return codes. Note that this function works best with raw input. */
+/* Grab a character from input without requiring the return key.
+ * May return KEYCODE_xxx values.
+ * Note that this function works best with raw input. */
 static int less_getch(int pos)
 {
        int i;