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 <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2009-05-17 16:44:54 +02:00
parent 071ede1e5d
commit 020f40693a
6 changed files with 116 additions and 35 deletions

View 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)

View 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 */

View 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

View 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) {

View 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;
}

View 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;