From 020f40693a08b836abdea74f3823a0bce0378ec5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 17 May 2009 16:44:54 +0200 Subject: [PATCH] line editing: add an option to emit ESC [ 6 n and use results 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 --- editors/vi.c | 13 +++++------ include/libbb.h | 13 ++++++++--- libbb/Config.in | 12 ++++++++++ libbb/lineedit.c | 42 +++++++++++++++++++++++++++-------- libbb/read_key.c | 57 +++++++++++++++++++++++++++++++++++++++++------- miscutils/less.c | 14 +++++------- 6 files changed, 116 insertions(+), 35 deletions(-) diff --git a/editors/vi.c b/editors/vi.c index ccc53fb58..ee5b5d98a 100644 --- a/editors/vi.c +++ b/editors/vi.c @@ -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) diff --git a/include/libbb.h b/include/libbb.h index bae7efb00..788140d14 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -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 */ diff --git a/libbb/Config.in b/libbb/Config.in index f5b804ff8..7ced387a9 100644 --- a/libbb/Config.in +++ b/libbb/Config.in @@ -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 diff --git a/libbb/lineedit.c b/libbb/lineedit.c index ccf3e0dc8..e1404fb68 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -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) { diff --git a/libbb/read_key.c b/libbb/read_key.c index fd100b0ec..3771045d2 100644 --- a/libbb/read_key.c +++ b/libbb/read_key.c @@ -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 " 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; } diff --git a/miscutils/less.c b/miscutils/less.c index 702c4a891..bd855066f 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -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; -- 2.25.1