IRB+RI: still room for improvement - better completion, method definition site discovery
Yes, everybody and his dog has hacked his .irbrc in order to use RI and complete methods. You've seen it in rubygarden and then in wirble.
But really, typing
Object.ri "object_id"
is way too much work.
sheepman showed the way to
combine method completion and RI, and here's my improvement on it: thanks to
FastRI*1
being much faster than RI, you can discover where a method came from and get
the corresponding documentation:

You use it like this:
>> 1.0.ri_[TAB] 1.0.ri_"%" 1.0.ri_class 1.0.ri_instance_variable_set 1.0.ri_respond_to? 1.0.ri_"*" 1.0.ri_clone 1.0.ri_instance_variables 1.0.ri_ri_ 1.0.ri_"**" 1.0.ri_coerce 1.0.ri_integer? 1.0.ri_round >> 1.0.ri_p[TAB] 1.0.ri_prec 1.0.ri_prec_i 1.0.ri_protected_methods 1.0.ri_prec_f 1.0.ri_private_methods 1.0.ri_public_methods
For each #method, you'll get the corresponding #ri_method completion. You can also do
>> r 'String#strip'
but now also with completion.
Finding out where the methods come from.
If you have
a = {}
with previous RI+irb hacks you might have tried
Hash.ri "inject"
Leaving the unneeded typing aside, you'd have obtained
Nothing known about Hash#inject
In other words: you'd have to know where the method comes from, that is, in which class/module it was defined. Now, that's easy for Enumerable#inject, but did you know about e.g. Precision#prec?
My irbrc hack looks for the method in all the ancestors of the receiver's class, so you don't need to know where the method you knew (maybe) nothing about (besides the name) to begin with comes from.
This is where FastRI enters the picture: the older RI can take over one second per lookup even when all your RI docs are cached and you don't hit the disk, and much longer (about 5 seconds for me) for the first one.
So
>> 1.0.ri_object_id
could take over 10 seconds with RI. It's under half a second for FastRI*2 -- and half of that time is actually spent formatting the description.
Indeed, when I use ri, I get this on my system:
>> t = Time.new; 1.0.ri_object_id; Time.new - t #(cold cache) ------------------------------------------------------- Object#object_id [...] => 10.798814 >> t = Time.new; 1.0.ri_object_id; Time.new - t #(.yaml cached) ------------------------------------------------------- Object#object_id [...] => 6.647412
RI is this slow because it searches all installed RubyGems packages.
The details
The finishing touches that make this more convenient:
- normal completion lists aren't polluted, and you won't see lots of ri_* in them.
- Module/Class objects are handled correctly
- "operator" methods are escaped
>> x = 1.0
=> 1.0
>> x.ri_
x.ri_"%" x.ri_class x.ri_instance_variable_set x.ri_respond_to?
x.ri_"*" x.ri_clone x.ri_instance_variables x.ri_ri_
x.ri_"**" x.ri_coerce x.ri_integer? x.ri_round
[...]
>> x.ri_"%"
---------------------------------------------------------------- Float#%
flt % other => float
flt.modulo(other) => float
[...]
>> x = x.ceil
=> 1
>> x.ri_"%"
--------------------------------------------------------------- Fixnum#%
fix % other => Numeric
fix.modulo(other) => Numeric
------------------------------------------------------------------------
Returns fix modulo other. See Numeric.divmod for more information.
- you also get completion for the "traditional" documentation mode
>> r 'String#do[TAB]
'String#downcase!' 'String#downcase'
>> r 'String#downcase'
-------------------------------------------------------- String#downcase
str.downcase => new_str
------------------------------------------------------------------------
Returns a copy of str with all uppercase letters replaced with
their lowercase counterparts. The operation is locale
insensitive---only characters ``A'' to ``Z'' are affected.
"hEllO".downcase #=> "hello"
The code
require 'irb/completion' module Kernel def r(arg) puts `fri "#{arg}"` end private :r end class Object def puts_ri_documentation_for(obj, meth) case self when Module candidates = ancestors.map{|klass| "#{klass}::#{meth}"} candidates.concat(class << self; ancestors end.map{|k| "#{k}##{meth}"}) else candidates = self.class.ancestors.map{|klass| "#{klass}##{meth}"} end candidates.each do |candidate| #puts "TRYING #{candidate}" desc = `fri '#{candidate}'` unless desc.chomp == "nil" # uncomment to use ri (and some patience) #desc = `ri -T '#{candidate}' 2>/dev/null` #unless desc.empty? puts desc return true end end false end private :puts_ri_documentation_for def method_missing(meth, *args, &block) if md = /ri_(.*)/.match(meth.to_s) unless puts_ri_documentation_for(self,md[1]) "Ri doesn't know about ##{meth}" end else super end end def ri_(meth) unless puts_ri_documentation_for(self,meth.to_s) "Ri doesn't know about ##{meth}" end end end RICompletionProc = proc{|input| bind = IRB.conf[:MAIN_CONTEXT].workspace.binding case input when /(\s*(.*)\.ri_)(.*)/ pre = $1 receiver = $2 meth = $3 ? /\A#{Regexp.quote($3)}/ : /./ #} begin candidates = eval("#{receiver}.methods", bind).map do |m| case m when /[A-Za-z_]/; m else # needs escaping %{"#{m}"} end end candidates = candidates.grep(meth) candidates.map{|s| pre + s } rescue Exception candidates = [] end when /([A-Z]\w+)#(\w*)/ #} klass = $1 meth = $2 ? /\A#{Regexp.quote($2)}/ : /./ candidates = eval("#{klass}.instance_methods(false)", bind) candidates = candidates.grep(meth) candidates.map{|s| "'" + klass + '#' + s + "'"} else IRB::InputCompletor::CompletionProc.call(input) end } #Readline.basic_word_break_characters= " \t\n\"\\'`><=;|&{(" Readline.basic_word_break_characters= " \t\n\\><=;|&" Readline.completion_proc = RICompletionProc
testing new BBS - mfp (2006-11-06 (Mon) 05:19:12)
Now the form sinks to the bottom as new posts are added. This should make cross-threading less attractive (hah don't you love those "cross-thread violation" posts on ruby-talk?).
***** - rubikitch (2006-11-06 (****) 07:25:58)
iHelp does almost the same thing.
mfp 2006-11-06 (Mon) 12:01:57
I'd missed kig's iHelp (check it out).
After a quick look at the code, it seems to me it does several things the above hack doesn't:
- it's self-contained
- it can open the pertinent ruby-doc.org page in your browser
- it can generate HTML
- it can use ruby2ruby to display method definitions
On the other hand,
- it doesn't do method completion
- it relies on the same code as RI and is therefore fairly slow
- (it's big)
When I release FastRI (and if the API stabilizes), I might send kig a patch to use FastRI::RiService instead.
kig 2006-11-27 (Mon) 15:45:37
Ooh, I like the method completion (just found this post today.) The RI slowness in IHelp only exhibits itself on require, after that lookups are sub-millisecond class.
******* - rubikitch (2006-11-06 (***) 18:30:36)
Edited by mfp: removed the repeated comment and reformatted code
class Object
define_method('a_"%"'){1}
def method_missing(meth, *args, &block)
[meth, args]
end
end
a_"*" # => [:a_, ["*"]]
a_"%" # => [:a_, ["%"]]
__send__ 'a_"*"' # => [:"a_\"*\"", []]
__send__ 'a_"%"' # => 1
print"a"
# >> a
What a nice hack it is!! I initially misunderstood a_"*" is a method, but actually a_ is a method and "*" is an argument. It is funny that this completion routine completes a method and an argument!
Hmmm - Steve (2006-11-16 (Thr) 14:57:50)
Can you post your Xdefaults/wmii theme? Thanks!
mfp 2006-11-16 (Thr) 15:11:13
Sure, here's the relevant part of my .Xdefaults:
! Rxvt Rxvt*color0: #000000 Rxvt*color1: #A80000 Rxvt*color2: #00A800 Rxvt*color3: #A8A800 Rxvt*color4: #0000A8 Rxvt*color5: #A800A8 Rxvt*color6: #00A8A8 Rxvt*color7: #A8A8A8 Rxvt*color8: #000054 Rxvt*color9: #FF0054 Rxvt*color10: #00FF54 Rxvt*color11: #FFFF54 Rxvt*color12: #0000FF Rxvt*color13: #FF00FF Rxvt*color14: #00FFFF Rxvt*color15: #FFFFFF Rxvt*font: -windows-proggyclean-medium-r-normal--13-80-96-96-c-70-iso8859-1,fixed Rxvt*boldFont: Rxvt*background: Black Rxvt*foreground: White Rxvt*scrollBar: false Rxvt*scrollTtyOutput: false Rxvt*scrollTtyKeypress: true Rxvt*scrollWithBuffer: true Rxvt*saveLines: 2000
and this is the wmii theme I'm using (with ruby-wmii):
WMII::Configuration.define do border 1 font "-windows-proggytinysz-medium-r-normal--10-80-96-96-c-60-iso8859-1" #font '-*-fixed-*-r-*-*-10-*-*-*-*-*-*-*' # _why's #selcolors '#FFFFFF #248047 #147027' #normcolors '#4D4E4F #DDDDAA #FFFFCC' # anthrazit selcolors '#eeeeee #506070 #708090' normcolors '#bbbbbb #222222 #000000' ...
Nice - Gavin (2007-01-28 (Sun) 15:38:46)
Nice work mfp! I look forward to trying this soon.
It occurred to me recently that another way to integrate IRB and FastRI would be to modify IRB to recognise calls to ri, something like
if line.strip =~ /^ri\s+(.*)/ call_fast_ri($1) else # normal IRB behaviour end
The benefit of that is that you can use RI without having to type those accursed quotes, as in
irb> ri String.upcase # instead of
# irb> ri 'String.upcase'
It seems like your work here is even more convenient than my idea, but I wonder what you think of it anyway.
Cheers, Gavin
mfp 2007-02-03 (Sat) 03:48:05
There's no reason we can't have both :)
I'm not sure IRB has got any hook to modify the input before it's processed though, would have to read the sources.
*1 soon to be released
*2 also keep in mind that I'm just launching several fri processes one after another, the actual lookups take virtually no time (something in the 1/100ths of a second), and what we're measuring is how long ruby takes to start and parse the code
- 40 http://mislav.caboo.se/rails/faster-ri-documentation
- 28 http://www.artima.com/forums/flat.jsp?forum=123&thread=183636
- 25 http://kakutani.com/20061108.html
- 23 http://anarchaia.org
- 23 http://technofinance.blogspot.com/2006/12/have-it-all-ruby-irb-autocompletion.html
- 18 http://www.middleastpost.com/246/links-for-2008-07-09
- 17 http://rubyblogs.org/aggy/tag/irb
- 9 http://www.artima.com/buzz/community.jsp?forum=123
- 7 http://www.rubycorner.com
- 4 http://planetruby.0x42.net
Keyword(s):[blog] [ruby] [frontpage] [irb] [ri] [completion] [irbrc] [snippet] [fastri]
References: