Short version:
I had a problem with bash/readline misinterpreting input sequences in vi mode and erasing half my input line. To fix it I got my terminal to send the less ambiguous "\eO[" instead of just "\e" for the escape key. But there was a hack in the readline code which was still causing problems, so I added the "vi-escape-hack" variable so I could disable the hack from my .inputrc. The patch for this variable is attached. Naturally readline's behaviour remains unchanged for the default value of the variable.

Long version:
I had a problem with the arrow/movement keys causing half my line to be wiped out in the vi insert mode of readline, which I experienced when using bash and mysql. It happened on average about once for an entire movement from the left to the right edge of my window, by holding the arrow key and letting it repeat.

I first encountered the problem with bash 4.0.38:
$ uname -srvmo
Linux 2.6.32.23-170.fc12.x86_64 #1 SMP Mon Sep 27 17:23:59 UTC 2010 x86_64 GNU/Linux
$ /bin/bash --version
GNU bash, version 4.0.38(1)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ ldd /bin/bash | grep readline

Then I built bash 4.2.0 from source to use an external readline 6.2:
$ ~/bin/bash --version
GNU bash, version 4.2.0(2)-release (x86_64-unknown-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ ldd ~/bin/bash | grep readline
libreadline.so.6 => /home/barry/fakeroot/lib/libreadline.so.6 (0x00007ff33e1a3000)

And I found that the problem was still present.  My config.h is attached.

I diagnosed the cause. It was due to the initial ESC byte of an arrow key sequence (\eOC for right or \eOD for left arrow) being interpreted as a press of the escape key, and the remainder as commands which clear to the end of the line.

Normally readline will wait an unlimited amount of time for additional input, and will only react once it is able to resolve this kind of ambiguity between different bound input sequences. For this special case of the escape key however, there is a special case in the code to give instant response, if there is nothing in the input queue after the escape byte. Otherwise the vi-mode user would not see a reaction to hitting the escape key until they gave additional input, and that would not be nice. Unfortunately, depending on your exact configuration, it may be possible, and may even be reasonably probable for the end of the input queue to be in the middle of an input sequence for a single key, when it is read. In this situation the arrow key can be misinterpreted as described above. I believe the chance of the problem may increase when bash is on the other side of a slow network link.

The editor vi uses a timeout system to deal with this, but that would require significant changes to the code, and even then it's not a perfect solution.

To eliminate the ambiguity, I told my terminal to generate a longer sequence (\eO[) when I hit escape. (For anyone interested, my terminal is mintty, I believe emulating an xterm, and I used this command: echo -e '\e[?7727h')
I set up appropriate readline bindings.

Then I added a variable "vi-escape-hack" to readline, which I turned off in my .inputrc to remove the special handling of escape.
The option is on by default, so default behaviour is unchanged.

After that I was able to use the arrow keys to navigate the line without the problem occurring again. Note though that since I can't unbind "\e" in .inputrc, it might still be possible to accidentally trigger that via a longer sequence that hasn't been bound to anything, but I haven't seen this.

I was hoping to get the vi-escape-hack option added to the official readline code. The patch is very simple. Just a few lines. It is based on readline-6.2. I named the variable as best I could, but I wouldn't mind if the name were changed.

# HG changeset patch
# User Barry Downes <[email protected]>
# Date 1332805294 -36000
# Node ID 82ce29506f914a550b0282949d9ebae558dc8f48
# Parent  c2d2ecd43ea58ecf8284b04ef248131cb2ea06b5
Add the vi-escape-hack boolean variable, which defaults to on.

diff -r c2d2ecd43ea5 -r 82ce29506f91 bind.c
--- a/bind.c    Fri Mar 23 19:27:34 2012 +1000
+++ b/bind.c    Tue Mar 27 09:41:34 2012 +1000
@@ -1433,6 +1433,7 @@
   { "expand-tilde",            &rl_complete_with_tilde_expansion, 0 },
   { "history-preserve-point",  &_rl_history_preserve_point,    0 },
   { "horizontal-scroll-mode",  &_rl_horizontal_scroll_mode,    0 },
+  { "vi-escape-hack",  &_rl_vi_escape_hack,    0 },
   { "input-meta",              &_rl_meta_flag,                 0 },
   { "mark-directories",                &_rl_complete_mark_directories, 0 },
   { "mark-modified-lines",     &_rl_mark_modified_lines,       0 },
diff -r c2d2ecd43ea5 -r 82ce29506f91 readline.c
--- a/readline.c        Fri Mar 23 19:27:34 2012 +1000
+++ b/readline.c        Tue Mar 27 09:41:34 2012 +1000
@@ -222,6 +222,10 @@
    AUDIBLE_BELL, or VISIBLE_BELL. */
 int _rl_bell_preference = AUDIBLE_BELL;
      
+/* When enabled, forces immediate reaction to the ESC character in the input, 
when it
+   is not followed by any other characters in the input queue. */
+int _rl_vi_escape_hack = 1;
+
 /* String inserted into the line by rl_insert_comment (). */
 char *_rl_comment_begin;
 
@@ -821,7 +825,7 @@
             will be if an arrow key has been pressed, and, if there's not,
             just dispatch to (what we assume is) rl_vi_movement_mode right
             away.  This is essentially an input test with a zero timeout. */
-         if (rl_editing_mode == vi_mode && key == ESC && map == 
vi_insertion_keymap
+         if (_rl_vi_escape_hack && rl_editing_mode == vi_mode && key == ESC && 
map == vi_insertion_keymap
              && _rl_input_queued (0) == 0)
            return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key)));
 #endif
diff -r c2d2ecd43ea5 -r 82ce29506f91 rlprivate.h
--- a/rlprivate.h       Fri Mar 23 19:27:34 2012 +1000
+++ b/rlprivate.h       Tue Mar 27 09:41:34 2012 +1000
@@ -446,6 +446,7 @@
 extern int _rl_horizontal_scroll_mode;
 extern int _rl_mark_modified_lines;
 extern int _rl_bell_preference;
+extern int _rl_vi_escape_hack;
 extern int _rl_meta_flag;
 extern int _rl_convert_meta_chars_to_ascii;
 extern int _rl_output_meta_chars;
/* config.h.  Generated from config.h.in by configure.  */
/* config.h.in.  Maintained by hand. */

/* Define NO_MULTIBYTE_SUPPORT to not compile in support for multibyte
   characters, even if the OS supports them. */
/* #undef NO_MULTIBYTE_SUPPORT */

/* #undef _FILE_OFFSET_BITS */

/* Define if on MINIX.  */
/* #undef _MINIX */

/* Define as the return type of signal handlers (int or void).  */
#define RETSIGTYPE void

#define VOID_SIGHANDLER 1

/* Characteristics of the compiler. */
/* #undef sig_atomic_t */

/* #undef size_t */

/* #undef ssize_t */

/* #undef const */

/* #undef volatile */

#define PROTOTYPES 1

/* #undef __CHAR_UNSIGNED__ */

/* Define if the `S_IS*' macros in <sys/stat.h> do not work properly.  */
/* #undef STAT_MACROS_BROKEN */

/* Define if you have the fcntl function. */
#define HAVE_FCNTL 1

/* Define if you have the getpwent function. */
#define HAVE_GETPWENT 1

/* Define if you have the getpwnam function. */
#define HAVE_GETPWNAM 1

/* Define if you have the getpwuid function. */
#define HAVE_GETPWUID 1

/* Define if you have the isascii function. */
#define HAVE_ISASCII 1

/* Define if you have the iswctype function.  */
#define HAVE_ISWCTYPE 1

/* Define if you have the iswlower function.  */
#define HAVE_ISWLOWER 1

/* Define if you have the iswupper function.  */
#define HAVE_ISWUPPER 1

/* Define if you have the isxdigit function. */
#define HAVE_ISXDIGIT 1

/* Define if you have the kill function. */
#define HAVE_KILL 1

/* Define if you have the lstat function. */
#define HAVE_LSTAT 1

/* Define if you have the mbrlen function. */
#define HAVE_MBRLEN 1

/* Define if you have the mbrtowc function. */
#define HAVE_MBRTOWC 1

/* Define if you have the mbsrtowcs function. */
#define HAVE_MBSRTOWCS 1

/* Define if you have the memmove function. */
#define HAVE_MEMMOVE 1

/* Define if you have the putenv function.  */
#define HAVE_PUTENV 1

/* Define if you have the select function.  */
#define HAVE_SELECT 1

/* Define if you have the setenv function.  */
#define HAVE_SETENV 1

/* Define if you have the setlocale function. */
#define HAVE_SETLOCALE 1

/* Define if you have the strcasecmp function.  */
#define HAVE_STRCASECMP 1

/* Define if you have the strcoll function.  */
#define HAVE_STRCOLL 1

/* #undef STRCOLL_BROKEN */

/* Define if you have the strpbrk function.  */
#define HAVE_STRPBRK 1

/* Define if you have the tcgetattr function.  */
#define HAVE_TCGETATTR 1

/* Define if you have the towlower function.  */
#define HAVE_TOWLOWER 1

/* Define if you have the towupper function.  */
#define HAVE_TOWUPPER 1

/* Define if you have the vsnprintf function.  */
#define HAVE_VSNPRINTF 1

/* Define if you have the wcrtomb function.  */
#define HAVE_WCRTOMB 1

/* Define if you have the wcscoll function.  */
#define HAVE_WCSCOLL 1

/* Define if you have the wctype function.  */
#define HAVE_WCTYPE 1

/* Define if you have the wcwidth function.  */
#define HAVE_WCWIDTH 1

#define STDC_HEADERS 1

/* Define if you have the <dirent.h> header file.  */
#define HAVE_DIRENT_H 1

/* Define if you have the <fcntl.h> header file.  */
#define HAVE_FCNTL_H 1

/* Define if you have the <langinfo.h> header file.  */
#define HAVE_LANGINFO_H 1

/* Define if you have the <limits.h> header file.  */
#define HAVE_LIMITS_H 1

/* Define if you have the <locale.h> header file.  */
#define HAVE_LOCALE_H 1

/* Define if you have the <memory.h> header file.  */
#define HAVE_MEMORY_H 1

/* Define if you have the <ndir.h> header file.  */
/* #undef HAVE_NDIR_H */

/* Define if you have the <pwd.h> header file.  */
#define HAVE_PWD_H 1

/* Define if you have the <stdarg.h> header file.  */
#define HAVE_STDARG_H 1

/* Define if you have the <stdlib.h> header file.  */
#define HAVE_STDLIB_H 1

/* Define if you have the <string.h> header file.  */
#define HAVE_STRING_H 1

/* Define if you have the <strings.h> header file.  */
#define HAVE_STRINGS_H 1

/* Define if you have the <sys/dir.h> header file.  */
/* #undef HAVE_SYS_DIR_H */

/* Define if you have the <sys/file.h> header file.  */
#define HAVE_SYS_FILE_H 1

/* Define if you have the <sys/ndir.h> header file.  */
/* #undef HAVE_SYS_NDIR_H */

/* Define if you have the <sys/pte.h> header file.  */
/* #undef HAVE_SYS_PTE_H */

/* Define if you have the <sys/ptem.h> header file.  */
/* #undef HAVE_SYS_PTEM_H */

/* Define if you have the <sys/select.h> header file.  */
#define HAVE_SYS_SELECT_H 1

/* Define if you have the <sys/stream.h> header file.  */
/* #undef HAVE_SYS_STREAM_H */

/* Define if you have the <termcap.h> header file.  */
/* #undef HAVE_TERMCAP_H */

/* Define if you have the <termio.h> header file.  */
#define HAVE_TERMIO_H 1

/* Define if you have the <termios.h> header file.  */
#define HAVE_TERMIOS_H 1

/* Define if you have the <unistd.h> header file.  */
#define HAVE_UNISTD_H 1

/* Define if you have the <varargs.h> header file.  */
/* #undef HAVE_VARARGS_H */

/* Define if you have the <wchar.h> header file.  */
#define HAVE_WCHAR_H 1

/* Define if you have the <wctype.h> header file.  */
#define HAVE_WCTYPE_H 1

#define HAVE_MBSTATE_T 1

/* Define if you have wchar_t in <wctype.h>. */
#define HAVE_WCHAR_T 1

/* Define if you have wctype_t in <wctype.h>. */
#define HAVE_WCTYPE_T 1

/* Define if you have wint_t in <wctype.h>. */  
#define HAVE_WINT_T 1

/* Define if you have <langinfo.h> and nl_langinfo(CODESET). */
#define HAVE_LANGINFO_CODESET 1

/* Definitions pulled in from aclocal.m4. */
#define VOID_SIGHANDLER 1

#define GWINSZ_IN_SYS_IOCTL 1

#define STRUCT_WINSIZE_IN_SYS_IOCTL 1

/* #undef STRUCT_WINSIZE_IN_TERMIOS */

/* #undef TIOCSTAT_IN_SYS_IOCTL */

#define FIONREAD_IN_SYS_IOCTL 1

/* #undef SPEED_T_IN_SYS_TYPES */

#define HAVE_GETPW_DECLS 1

/* #undef STRUCT_DIRENT_HAS_D_INO */

/* #undef STRUCT_DIRENT_HAS_D_FILENO */

/* #undef HAVE_BSD_SIGNALS */

#define HAVE_POSIX_SIGNALS 1

/* #undef HAVE_USG_SIGHOLD */

/* #undef MUST_REINSTALL_SIGHANDLERS */

#define HAVE_POSIX_SIGSETJMP 1

#define CTYPE_NON_ASCII 1

/* modify settings or make new ones based on what autoconf tells us. */

/* Ultrix botches type-ahead when switching from canonical to
   non-canonical mode, at least through version 4.3 */
#if !defined (HAVE_TERMIOS_H) || !defined (HAVE_TCGETATTR) || defined (ultrix)
#  define TERMIOS_MISSING
#endif

#if defined (__STDC__) && defined (HAVE_STDARG_H)
#  define PREFER_STDARG
#  define USE_VARARGS
#else
#  if defined (HAVE_VARARGS_H)
#    define PREFER_VARARGS
#    define USE_VARARGS
#  endif
#endif
_______________________________________________
Bug-readline mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/bug-readline

Reply via email to