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

···

--
Posted via http://www.ruby-forum.com/.

Hi,

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

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

CompletionProc = proc {|input| input}

=> #<Proc:0x0003f034@(irb):9>

Readline.completion_proc = CompletionProc

=> #<Proc:0x0003f034@(irb):9>

Readline.completion_proc.call("req")

=> "req"

req # tab clicked

req # return value

A real world example exists on irb/completion.rb

hth. regards, Sandor Szücs

···

On 17.07.2008, at 18:44, Marc Heiler wrote:
--

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.

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

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

···

On 17.07.2008, at 18:44, Marc Heiler wrote:
--

I should be even more specific.

···

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

"cd foo<TAB>" # completed to foobar

but if the user would type

"blabalblabla foo<TAB>" # 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.

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

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.

Hi,

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

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.

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.

···

On 2008/08/11, at 13:42, Takao Kouji wrote:
-----

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

Hello Takao Kouji,

Sorry for the late reply.

Thanks a lot!

···

--
Posted via http://www.ruby-forum.com/.

Hi,

Sorry for late reply.

···

On 2008/08/12, at 17:17, Takao Kouji wrote:

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

I commited it in r24019.

Thanks, Kouji.

---
TAKAO Kouji <kouji@takao7.net>
blog: http://d.hatena.ne.jp/kouji0625/
twitter: takaokouji / projects: ruby, s7-seven