Readline and conditional tab results based on input

Hi there,

is there a way to “find out” what a user already typed in readline?

Reason is, i am fetching user input like so:

user_input = Readline.readline(’ Input name of a dir, then press
'+"\n",true)

And with Readline.completion_proc i have code that will return only dirs
in this directory. But I would like to invoke this specific code part
only
when a user typed in i.e. "cd "

So if i have a directory called “foobar” it should work here:

“cd foo” # completed to foobar

but if the user would type

“blabalblabla foo” # there should be no result

Hi,

On 17.07.2008, at 18:44, Marc H. wrote:

is there a way to “find out” what a user already typed in readline?

I think you have to implement a CompletionProc.
simple example:

irb> CompletionProc = proc {|input| input}
=> #Proc:0x0003f034@:9(irb)
irb> Readline.completion_proc = CompletionProc
=> #Proc:0x0003f034@:9(irb)
irb> Readline.completion_proc.call(“req”)
=> “req”
irb> req # tab clicked
req # return value

A real world example exists on irb/completion.rb

hth. regards, Sandor
Szücs

Hi,

Sorry for my first post, it wasn’t what you not already knew.
I should better read then write, but I hope on the bottom I found
something that help.

On 17.07.2008, at 18:44, Marc H. wrote:

in this directory. But I would like to invoke this specific code part
“blabalblabla foo” # there should be no result
I tested a bit your scenario and I think you have to set
Readline.completer_word_break_characters , but not to " \t\n"
'><=;|&{(" If you set it to "\t\n\"\\'><=;|&{(", without whitespace, you will
get as
input in your CompletionProc the whole line.

Then you can do:

when /^cd/
typed = input.sub(“cd”,"").strip

$ tabcompletion_test.rb
cd bl|cd bl| # tab called after ‘cd bl’

$ cat tabcompletion_test.rb

require ‘readline’

module MYCMD
ReservedWords = [
“BEGIN”, “END”, “yield”,
]

CompletionProc = proc { |input|
puts("|#{input}|")
case input
when /^cd/
typed = input.sub(“cd”,"")
if typed==""
candidates = Dir.entries(".")
elsif typed == " "
candidates = Dir.entries(".")
else
candidates = [“no”]
end
else
(candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/)
end
}

 def self.select_message(receiver, message, candidates)
   candidates.grep(/^#{message}/).collect do |e|
   puts("e:#{e}")
   case e
   when /^[a-zA-Z_]/
     receiver + "." + e
   end
 end

end
end

no ’ ’

Readline.completer_word_break_characters= “\t\n”\’`><=;|&{("
Readline.completion_append_character = nil
Readline.completion_proc = MYCMD::CompletionProc

loop do
cmd = Readline.readline
eval(cmd.chomp)
end

regards, Sandor
Szücs

I should be even more specific.

On 18.07.2008, at 21:15, Sandor Szücs wrote:

“cd foo” # completed to foobar

but if the user would type

“blabalblabla foo” # there should be no result

require 'readline'

module MYCMD

CompletionProc = proc { |input|
   case input
   when /^cd/
     typed = input.sub("cd","")
     typed.strip!
     save = input.sub(/#{typed}\z/,"")
     fragment = File.basename(typed)
     tmp = Dir.entries(File.dirname(typed))
     candidates = tmp.select {|f| f.match(/^#{Regexp.quote(fragment)}/)}
     candidates.map! {|f| save+File.dirname(typed)+"/"+f+"/"}
   end
}
#Readline.completer_word_break_characters=  "\t\n\"\\'`><=;|&{("
Readline.completer_word_break_characters=  "\t"
Readline.completion_append_character = nil
Readline.completion_proc = MYCMD::CompletionProc

loop do
   cmd = Readline.readline
   eval(cmd.chomp)
end

That was what you have expected, or?

regards, Sandor
Szücs

Hi, I am the maintainer of Readline module.

but if the user would type

“blabalblabla foo” # there should be no result

I want to add some methods that satisfy the Marc H.'s demands.
The patch contributed to the RubyForge is taken.

[#3212] Readline does not provide enough context to the
completion_proc
http://rubyforge.org/tracker/index.php?func=detail&aid=3212&group_id=426&atid=1698

First of all, I will take it into ruby 1.9. Afterwards, backport to 1.8.

Thanks, TAKAO Kouji.

Hi,

On 2008/08/11, at 13:42, Takao K. wrote:

but if the user would type

“blabalblabla foo” # there should be no result

I want to add some methods that satisfy the Marc H.'s demands.
The patch contributed to the RubyForge is taken.

[#3212] Readline does not provide enough context to the completion_proc
http://rubyforge.org/tracker/index.php?func=detail&aid=3212&group_id=426&atid=1698

First of all, I will take it into ruby 1.9. Afterwards, backport to 1.8.

I will add the follows methods.

  • line_buffer: Returns the full line that is being edited.
    (same as rl_line_buffer)
  • point: Returns the index of the current cursor position
    in Readline.line_buffer. (same as rl_point)

The match_start is computed by subtracting the length of input-string
from Readline.point.
The match_end and Readline.line_buffer are same.

Attached the patch for ruby 1.9 (r18525).

Thanks, TAKAO Kouji.

Index: ext/readline/readline.c

— ext/readline/readline.c (revision 18525)
+++ ext/readline/readline.c (working copy)
@@ -367,6 +367,56 @@
return rb_attr_get(mReadline, completion_case_fold);
}

+/*

    • call-seq:
    • Readline.line_buffer → string
    • Returns the full line that is being edited. This is useful from
    • within the complete_proc for determining the context of the
    • completion request.
    • The length of +Readline.line_buffer+ and GNU Readline’s rl_end are
    • same.
  • */
    +static VALUE
    +readline_s_get_line_buffer(VALUE self)
    +{
    +#ifdef HAVE_RL_LINE_BUFFER
  • rb_secure(4);
  • if (rl_line_buffer == NULL)
  • return Qnil;
  • return rb_tainted_str_new2(rl_line_buffer);
    +#else
  • rb_notimplement();
  • return Qnil; /* not reached /
    +#endif /
    HAVE_RL_LINE_BUFFER */
    +}

+/*

    • call-seq:
    • Readline.point → int
    • Returns the index of the current cursor position in
    • +Readline.line_buffer+.
    • The index in +Readline.line_buffer+ which matches the start of
    • input-string passed to completion_proc is computed by subtracting
    • the length of input-string from +Readline.point+.
    • start = (the length of input-string) - Readline.point
  • */
    +static VALUE
    +readline_s_get_point(VALUE self)
    +{
    +#ifdef HAVE_RL_POINT
  • rb_secure(4);
  • return INT2NUM(rl_point);
    +#else
  • rb_notimplement();
  • return Qnil; /* not reached /
    +#endif /
    HAVE_RL_POINT */
    +}

static char **
readline_attempted_completion_function(const char *text, int start, int
end)
{
@@ -1196,6 +1246,10 @@
readline_s_set_completion_case_fold, 1);
rb_define_singleton_method(mReadline, “completion_case_fold”,
readline_s_get_completion_case_fold, 0);

  • rb_define_singleton_method(mReadline, “line_buffer”,
  •         readline_s_get_line_buffer, 0);
    
  • rb_define_singleton_method(mReadline, “point”,
  •         readline_s_get_point, 0);
    
    rb_define_singleton_method(mReadline, “vi_editing_mode”,
    readline_s_vi_editing_mode, 0);
    rb_define_singleton_method(mReadline, “vi_editing_mode?”,
    Index: ext/readline/extconf.rb
    ===================================================================
    — ext/readline/extconf.rb (revision 18525)
    +++ ext/readline/extconf.rb (working copy)
    @@ -59,6 +59,8 @@
    have_readline_var(“rl_attempted_completion_over”)
    have_readline_var(“rl_library_version”)
    have_readline_var(“rl_editing_mode”)
    +have_readline_var(“rl_line_buffer”)
    +have_readline_var(“rl_point”)

workaround for native windows.

/mswin|bccwin|mingw/ !~ RUBY_PLATFORM &&
have_readline_var(“rl_event_hook”)
have_readline_func(“rl_cleanup_after_signal”)
Index: test/readline/test_readline.rb

— test/readline/test_readline.rb (revision 18525)
+++ test/readline/test_readline.rb (working copy)
@@ -3,6 +3,8 @@
=begin
class << Readline
[

  • "line_buffer",
    
  • "point",
    "vi_editing_mode",
    "emacs_editing_mode",
    "completion_append_character=",
    

@@ -61,6 +63,8 @@
[“completer_quote_characters”],
[“filename_quote_characters=”, “\”],
[“filename_quote_characters”],

  •   ["line_buffer"],
    
  •   ["point"],
     ]
    
    method_args.each do |method_name, *args|
    assert_raises(SecurityError, NotImplementedError,
    @@ -74,41 +78,83 @@
    end
    end
  • def test_readline
  • stdin = Tempfile.new(“test_readline_stdin”)
  • stdout = Tempfile.new(“test_readline_stdout”)
  • begin
  •  stdin.write("hello\n")
    
  •  stdin.close
    
  •  stdout.close
    
  •  line = replace_stdio(stdin.path, stdout.path) {
    
  •    Readline.readline("> ", true)
    
  •  }
    
  •  assert_equal("hello", line)
    
  •  assert_equal(true, line.tainted?)
    
  •  stdout.open
    
  •  assert_equal("> ", stdout.read(2))
    
  •  assert_equal(1, Readline::HISTORY.length)
    
  •  assert_equal("hello", Readline::HISTORY[0])
    
  •  assert_raises(SecurityError) do
    
  •    Thread.start {
    
  •      $SAFE = 1
    
  •      replace_stdio(stdin.path, stdout.path) do
    
  •        Readline.readline("> ".taint)
    
  •      end
    
  •    }.join
    
  • if !/EditLine/n.match(Readline::VERSION)
  • def test_readline
  •  stdin = Tempfile.new("test_readline_stdin")
    
  •  stdout = Tempfile.new("test_readline_stdout")
    
  •  begin
    
  •    stdin.write("hello\n")
    
  •    stdin.close
    
  •    stdout.close
    
  •    line = replace_stdio(stdin.path, stdout.path) {
    
  •      Readline.readline("> ", true)
    
  •    }
    
  •    assert_equal("hello", line)
    
  •    assert_equal(true, line.tainted?)
    
  •    stdout.open
    
  •    assert_equal("> ", stdout.read(2))
    
  •    assert_equal(1, Readline::HISTORY.length)
    
  •    assert_equal("hello", Readline::HISTORY[0])
    
  •    assert_raises(SecurityError) do
    
  •      Thread.start {
    
  •        $SAFE = 1
    
  •        replace_stdio(stdin.path, stdout.path) do
    
  •          Readline.readline("> ".taint)
    
  •        end
    
  •      }.join
    
  •    end
    
  •    assert_raises(SecurityError) do
    
  •      Thread.start {
    
  •        $SAFE = 4
    
  •        replace_stdio(stdin.path, stdout.path) { 
    

Readline.readline("> ") }

  •      }.join
    
  •    end
    
  •  ensure
    
  •    stdin.close(true)
    
  •    stdout.close(true)
     end
    
  •  assert_raises(SecurityError) do
    
  •    Thread.start {
    
  •      $SAFE = 4
    
  •      replace_stdio(stdin.path, stdout.path) { Readline.readline("> 
    

") }

  •    }.join
    
  • end
  • line_buffer

  • point

  • def test_line_buffer__point
  •  begin
    
  •    Readline.line_buffer
    
  •    Readline.point
    
  •  rescue NotImplementedError
    
  •    return
     end
    
  • ensure
  •  stdin.close(true)
    
  •  stdout.close(true)
    
  •  stdin = Tempfile.new("test_readline_stdin")
    
  •  stdout = Tempfile.new("test_readline_stdout")
    
  •  begin
    
  •    actual_text = nil
    
  •    actual_line_buffer = nil
    
  •    actual_point = nil
    
  •    Readline.completion_proc = proc { |text|
    
  •      actual_text = text
    
  •      actual_point = Readline.point
    
  •      actual_buffer_line = Readline.line_buffer
    
  •      stdin.write(" finish\n")
    
  •      stdin.close
    
  •      stdout.close
    
  •      return ["complete"]
    
  •    }
    
  •    stdin.write("first second\t")
    
  •    stdin.flush
    
  •    line = replace_stdio(stdin.path, stdout.path) {
    
  •      Readline.readline("> ", false)
    
  •    }
    
  •    assert_equal("first second", actual_line_buffer)
    
  •    assert_equal(12, actual_point)
    
  •    assert_equal("first complete finish", Readline.line_buffer)
    
  •    assert_equal(21, Readline.point)
    
  •  ensure
    
  •    stdin.close(true)
    
  •    stdout.close(true)
    
  •  end
    
    end
  • end if !/EditLine/n.match(Readline::VERSION)
  • end

    def test_input=
    assert_raise(TypeError) do

Hi,

Sorry for late reply.

On 2008/08/12, at 17:17, Takao K. wrote:

Attached the patch for ruby 1.9 (r18525).
I commited it in r24019.

Thanks, Kouji.


TAKAO Kouji [email protected]
blog: 高尾宏治日記 on はてな
twitter: takaokouji / projects: ruby, s7-seven

Hello Takao K.,

Sorry for the late reply.

Thanks a lot!