Readline and conditional tab results based on input

M

Marc Heiler

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
 
S

Sandor Szücs

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:

irb> CompletionProc =3D proc {|input| input}
=3D> #<Proc:0x0003f034@(irb):9>
irb> Readline.completion_proc =3D CompletionProc=09
=3D> #<Proc:0x0003f034@(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
--=
 
S

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.

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 =3D Readline.readline(' Input name of a dir, then press
<TAB>'+"\n",true)

And with Readline.completion_proc i have code that will return only =20=
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\"\=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
--
 
S

Sandor Szücs

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

Takao Kouji

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

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

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
 
T

Takao Kouji

Hi,

Sorry for late reply.

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.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,023
Latest member
websitedesig25

Latest Threads

Top