Readline and conditional tab results based on input

Discussion in 'Ruby' started by Marc Heiler, Jul 17, 2008.

  1. Marc Heiler

    Marc Heiler Guest

    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
    <TAB>'+"\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<TAB>" # completed to foobar

    but if the user would type

    "blabalblabla foo<TAB>" # there should be no result
     
    Marc Heiler, Jul 17, 2008
    #1
    1. Advertisements

  2. Hi,

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

    irb> CompletionProc =3D proc {|input| input}
    =3D> #<Proc:[email protected](irb):9>
    irb> Readline.completion_proc =3D CompletionProc=09
    =3D> #<Proc:[email protected](irb):9>
    irb> Readline.completion_proc.call("req")
    =3D> "req"
    irb> req # tab clicked
    req # return value

    A real world example exists on irb/completion.rb


    hth. regards, Sandor Sz=FCcs
    --=
     
    Sandor Szücs, Jul 18, 2008
    #2
    1. Advertisements

  3. 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.

    I tested a bit your scenario and I think you have to set
    Readline.completer_word_break_characters , but not to " \t\n\"\=20
    \'`><=3D;|&{("
    If you set it to "\t\n\"\\'`><=3D;|&{(", without whitespace, you will =20=

    get as
    input in your CompletionProc the whole line.

    Then you can do:

    when /^cd/
    typed =3D 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 =3D [
    "BEGIN", "END", "yield",
    ]


    CompletionProc =3D proc { |input|
    puts("|#{input}|")
    case input
    when /^cd/
    typed =3D input.sub("cd","")
    if typed=3D=3D""
    candidates =3D Dir.entries(".")
    elsif typed =3D=3D " "
    candidates =3D Dir.entries(".")
    else
    candidates =3D ["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=3D "\t\n\"\\'`><=3D;|&{("
    Readline.completion_append_character =3D nil
    Readline.completion_proc =3D MYCMD::CompletionProc=09

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


    regards, Sandor Sz=FCcs
    --
     
    Sandor Szücs, Jul 18, 2008
    #3
  4. I should be even more specific.


    ~~~~~~~~~~~~~~~~~~~~~
    require 'readline'

    module MYCMD

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

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

    That was what you have expected, or?

    regards, Sandor Sz=FCcs
    --=
     
    Sandor Szücs, Jul 18, 2008
    #4
  5. Marc Heiler

    Takao Kouji Guest

    Hi, I am the maintainer of Readline module.
    I want to add some methods that satisfy the Marc Heiler'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.
     
    Takao Kouji, Aug 11, 2008
    #5
  6. Marc Heiler

    Takao Kouji Guest

    Hi,

    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
     
    Takao Kouji, Aug 12, 2008
    #6
  7. Marc Heiler

    Marc Heiler Guest

    Hello Takao Kouji,

    Sorry for the late reply.

    Thanks a lot!
     
    Marc Heiler, Oct 17, 2008
    #7
  8. Marc Heiler

    Takao Kouji Guest

    Hi,

    Sorry for late reply.

    I commited it in r24019.

    Thanks, Kouji.
     
    Takao Kouji, Jul 10, 2009
    #8
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.