Project

General

Profile

Actions

Feature #3616

closed

IRB + readline incorrectly counts non-printing characters in prompt

Added by guns (Sung Pae) over 13 years ago. Updated almost 13 years ago.

Status:
Closed
Target version:
-
[ruby-core:31484]

Description

=begin
When setting a prompt for IRB that contains terminal control characters:

 IRB.conf[:PROMPT][:COLOR] = {
   :PROMPT_I => "\e[0;1;32m>>> \e[0m",
   ...
 }
 IRB.conf[:PROMPT_MODE] = :COLOR

IRB includes "\e[...m" in the prompt length calculation, causing readline
functions like beginning-of-line to move to the incorrect place on the line:

 >>> 012345678901234567890
                          ^ caret
 <Press \C-a>
 >>> 012345678901234567890
                  ^ caret

Fortunately, GNU Readline offers a couple of constants to mark that a string
should be non-printing:

 # readline.h

 /* Definitions available for use by readline clients. */
 #define RL_PROMPT_START_IGNORE	'\001'
 #define RL_PROMPT_END_IGNORE	'\002'

Bash handles this issue by mapping \[' and ]' to RL_PROMPT_START_IGNORE and
RL_PROMPT_END_IGNORE inside of `PS*' prompt variables. It would be nice to offer
the same feature in IRB, or simply to surround all terminal escape sequences in
the IRB prompt with RL_PROMPT_*_IGNORE.

Note that this bug does not occur with libedit.
=end

Actions #1

Updated by nobu (Nobuyoshi Nakada) over 13 years ago

=begin
Hi,

At Mon, 26 Jul 2010 14:56:14 +0900,
Sung Pae wrote in [ruby-core:31484]:

Bash handles this issue by mapping \[' and ]' to RL_PROMPT_START_IGNORE and
RL_PROMPT_END_IGNORE inside of `PS*' prompt variables. It would be nice to offer
the same feature in IRB, or simply to surround all terminal escape sequences in
the IRB prompt with RL_PROMPT_*_IGNORE.

Even if it is implemented, you'll have to write as:

   :PROMPT_I => "\\[\e[0;1;32m\\]>>> \\[\e[0m\\]"

It doesn't seem nice.

--
Nobu Nakada

=end

Actions #2

Updated by guns (Sung Pae) over 13 years ago

=begin

Even if it is implemented, you'll have to write as:

  :PROMPT_I => "\\[\e[0;1;32m\\]>>> \\[\e[0m\\]"

It doesn't seem nice.

Yes, I agree. That is very ugly. I think it's appropriate to simply surround all
terminal escape sequences with the special RL values. Also, \001 and \002 do not
seem to affect libedit at all.

It is relatively simple to patch, so if the maintainer does not wish to bother,
I can supply a patch later in the week after my own deadlines are past.

guns

=end

Actions #3

Updated by nobu (Nobuyoshi Nakada) over 13 years ago

=begin
Hi,

At Tue, 27 Jul 2010 08:34:08 +0900,
Sung Pae wrote in [ruby-core:31509]:

Yes, I agree. That is very ugly. I think it's appropriate to simply surround all
terminal escape sequences with the special RL values. Also, \001 and \002 do not
seem to affect libedit at all.

OK. I'll yield this ticket to the maintainer of ext/readline.


diff --git a/ext/readline/readline.c b/ext/readline/readline.c
index 3ecea94..9b154bc 100644
--- a/ext/readline/readline.c
+++ b/ext/readline/readline.c
@@ -43,10 +43,20 @@
static VALUE mReadline;

#define EDIT_LINE_LIBRARY_VERSION "EditLine wrapper"
+#ifndef USE_INSERT_IGNORE_ESCAPE
+# ifndef HAVE_EDITLINE_READLINE_H
+# define USE_INSERT_IGNORE_ESCAPE 1
+# else
+# define USE_INSERT_IGNORE_ESCAPE 0
+# endif
+#endif

#define COMPLETION_PROC "completion_proc"
#define COMPLETION_CASE_FOLD "completion_case_fold"
static ID completion_proc, completion_case_fold;
+#if USE_INSERT_IGNORE_ESCAPE
+static ID id_orig_prompt, id_last_prompt;
+#endif

#ifndef HAVE_RL_FILENAME_COMPLETION_FUNCTION

define rl_filename_completion_function filename_completion_function

@@ -145,6 +155,81 @@ readline_event(void)
}
#endif

+#if USE_INSERT_IGNORE_ESCAPE
+static VALUE
+insert_ignore_escape(VALUE self, VALUE prompt)
+{

  • VALUE last_prompt, orig_prompt = rb_attr_get(self, id_orig_prompt);
  • int ignoring = 0;
  • const char *s0, *s, *e;
  • long len;
  • static const char ignore_code[2] = {RL_PROMPT_START_IGNORE, RL_PROMPT_END_IGNORE};
  • prompt = rb_str_new_shared(prompt);
  • last_prompt = rb_attr_get(self, id_last_prompt);
  • if (orig_prompt == prompt) return last_prompt;
  • len = RSTRING_LEN(prompt);
  • if (NIL_P(last_prompt)) {
  • last_prompt = rb_str_tmp_new(len);
  • }
  • s = s0 = RSTRING_PTR(prompt);
  • e = s0 + len;
  • rb_str_set_len(last_prompt, 0);
  • while (s < e && *s) {
  • switch (*s) {
  • case RL_PROMPT_START_IGNORE:
  •  ignoring = -1;
    
  •  rb_str_cat(last_prompt, s0, ++s - s0);
    
  •  s0 = s;
    
  •  break;
    
  • case RL_PROMPT_END_IGNORE:
  •  ignoring = 0;
    
  •  rb_str_cat(last_prompt, s0, ++s - s0);
    
  •  s0 = s;
    
  •  break;
    
  • case '\033':
  •  if (++s < e && *s == '[') {
    
  •  rb_str_cat(last_prompt, s0, s - s0 - 1);
    
  •  s0 = s - 1;
    
  •  while (++s < e && *s) {
    
  •      if (ISALPHA(*s)) {
    
  •  	if (!ignoring) {
    
  •  	    ignoring = 1;
    
  •  	    rb_str_cat(last_prompt, ignore_code+0, 1);
    
  •  	}
    
  •  	rb_str_cat(last_prompt, s0, ++s - s0);
    
  •  	s0 = s;
    
  •  	break;
    
  •      }
    
  •      else if (!('0' <= *s && *s <= '9' || *s == ';')) {
    
  •  	break;
    
  •      }
    
  •  }
    
  •  }
    
  •  break;
    
  • default:
  •  if (ignoring > 0) {
    
  •  ignoring = 0;
    
  •  rb_str_cat(last_prompt, ignore_code+1, 1);
    
  •  }
    
  •  s++;
    
  •  break;
    
  • }
  • }
  • if (ignoring > 0) {
  • ignoring = 0;
  • rb_str_cat(last_prompt, ignore_code+1, 1);
  • }
  • rb_str_cat(last_prompt, s0, s - s0);
  • rb_ivar_set(self, id_orig_prompt, prompt);
  • rb_ivar_set(self, id_last_prompt, last_prompt);
  • return last_prompt;
    +}
    +#endif

static VALUE
readline_get(VALUE prompt)
{
@@ -248,6 +333,10 @@ readline_readline(int argc, VALUE *argv, VALUE self)
rb_secure(4);
if (rb_scan_args(argc, argv, "02", &tmp, &add_hist) > 0) {
OutputStringValue(tmp);
+#if USE_INSERT_IGNORE_ESCAPE

  • tmp = insert_ignore_escape(self, tmp);
  • rb_str_locktmp(tmp);
    +#endif
    prompt = RSTRING_PTR(tmp);
    }

@@ -257,6 +346,11 @@ readline_readline(int argc, VALUE argv, VALUE self)
rl_prep_terminal(1);
#endif
buff = (char
)rb_protect(readline_get, (VALUE)prompt, &status);
+#if USE_INSERT_IGNORE_ESCAPE

  • if (prompt) {
  • rb_str_unlocktmp(tmp);
  • }
    +#endif
    if (status) {
    #if defined HAVE_RL_CLEANUP_AFTER_SIGNAL
    /* restore terminal mode and signal handler*/
    @@ -1381,6 +1475,11 @@ Init_readline()
    rb_define_singleton_method(mReadline, "refresh_line",
    readline_s_refresh_line, 0);

+#if USE_INSERT_IGNORE_ESCAPE

  • CONST_ID(id_orig_prompt, "orig_prompt");
  • CONST_ID(id_last_prompt, "last_prompt");
    +#endif
  • history = rb_obj_alloc(rb_cObject);
    rb_extend_object(history, rb_mEnumerable);
    rb_define_singleton_method(history,"to_s", hist_to_s, 0);

--
Nobu Nakada

=end

Actions #4

Updated by nobu (Nobuyoshi Nakada) over 13 years ago

  • Category set to ext
  • Status changed from Open to Assigned
  • Assignee set to kouji (Kouji Takao)

=begin

=end

Actions #5

Updated by kouji (Kouji Takao) over 13 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

=begin
This issue was solved with changeset r30496.
Sung, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.

=end

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0