Bond - From Irb With Completion Love
Bond is on a mission, as usual. This time it’s to make custom autocompletion dead easy in Irb. Simply order Bond to complete arguments, methods or your own twisted regular expressions and they will be executed. No questions asked.
Bond’s Coordinates
Bond’s getting old nowadays, regardless of what Hollywood says. So old that I even got his coordinates:
gem install bond
Debrief
Let’s say you want to autocomplete some quoted arguments for a couple of methods. Here’s how you could do that in Irb:
# Get rid of space and quotes as word delimiters in order to read the full line
Readline.basic_word_break_characters = "\t\n`><=;|&{("
Readline.completion_proc = proc {|input|
begin
case input
when /^\s*(method1)\s*(['"]?)(.*)$/
meth, quote = $1, $2
%w{some args to autocomplete}.grep(/^#{$3}/).map {|e| "#{meth} #{quote}" + e}
when /^\s*(method2)\s*(['"]?)(.*)$/
meth, quote = $1, $2
%w{more args to autocomplete}.grep(/^#{$3}/).map {|e| "#{meth} #{quote}" + e}
else
# Default to irb's default autocompletion
IRB::InputCompletor::CompletionProc.call(input)
end
# irb doesn't handle failed completions nicely
rescue Exception
IRB::InputCompletor::CompletionProc.call(input)
end
}
In irb, this would look like:
bash> irb -rirb/completion >> # Copy and paste the above code # Autocompleting method1 >> method1 [TAB] method1 args method1 autocomplete method1 some method1 to >> method1 'a[TAB] method1 'args method1 'autocomplete >> method1 'au[TAB] >> method1 'autocomplete
So is the above completion_proc
horrible? No, as long as you don’t mind the way the autocompletion looks and playing with Readline and Irb configuration. Assuming you do, what happens when you start adding more completion cases? Do you really want to string together a long chain of case statements as irb does?
Let’s have Bond take a shot at the above:
# We are loading irb/completion to match the above. Bond can work fine without it in irb. bash> irb -rirb/completion -rubygems # This loads Bond but it doesn't take over completion yet. >> require 'bond' # For Bond to handle completions, we must explicitly define a completion mission. # Order matters since the order they're declared in is the order they're searched. >> Bond.complete(:method=>'method1') {|input| %w{some args to autocomplete} } => true >> Bond.complete(:method=>'method2') {|input| %w{more args to autocomplete} } => true # Works as above >> method1 [TAB] args autocomplete some to >> method1 'a[TAB] args autocomplete >> method1 'au[TAB] >> method1 'autocomplete' # Anything not matched by the above completion missions defaults to irb completion >> $std[TAB] $stderr $stdin $stdout
Nice, autocompletion actually looks like it does in normal shells! Note that Bond.complete
searches your input for you by default. We’ll talk more about this later.
For Completion Geeks:
In the first example above, notice that the possible completions are the full lines of input. This happens because we have to change the definition of a word to get the full line. We have to hack the word definition because none of the stable Ruby versions have a method to fetch Readline’s full line buffer. Thankfully, Csaba Henk wrote up a C extension for this in his Irb enhancements. Bond comes armed with this C extension. As a side note, future versions of Ruby will come with this Readline enhancement thanks to Takao Kouji’s recent commit.
Autocompleting Files as Arguments
So we know Bond can handle irb’s basic methods. What about class methods?
# Autocomplete with files/directories in the current directory >> Bond.complete(:method=>"File.read") {|e| Dir.entries(Dir.pwd) } => true # Let's use it >> File.read '[TAB] .git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/ >> File.read 'L[TAB] >> File.read 'LICENSE.txt'
That’s pretty easy. Now what if we want to autocomplete through directories like we do in our shells? We could write one ourself or just use Readline::FILENAME_COMPLETION_PROC
:
# Pass :search=>false to turn off Bond searching since FILENAME_COMPLETION_PROC does it for us. >> Bond.recomplete(:method=>"File.read", :search=>false) {|input| Readline::FILENAME_COMPLETION_PROC.call(input) || [] } => true # Test drive it >> File.read '[TAB] .git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/ >> File.read 'l[TAB] >> File.read 'lib/ >> File.read 'lib/bond.[TAB] >> File.read 'lib/bond.rb' # Since File.read doesn't understand ~, let's improve the above completion proc >> file_completion = proc {|input| (Readline::FILENAME_COMPLETION_PROC.call(input) || []).map {|f| f =~ /^~/ ? File.expand_path(f) : f } } => #< Proc:0x0100f1d0@(irb):20> >> Bond.recomplete :method=>"File.read", :search=>false, &file_completion => true # Tilda test driving >> File.read "~/[TAB] >> File.read "/Users/bozo/ >> File.read "/Users/bozo/.alias.[TAB] >> File.read "/Users/bozo/.alias.yml"
Nice! Now that we have a decent file autocompletion let’s add it to all our File methods:
>> Bond.reset; Bond.complete :method=>/File\.(#{Regexp.union(*File.methods(false))})/, :search=>false, &file_completion => true # Normal irb method autocompletion still works >> File.f[TAB] File.file? File.fnmatch File.fnmatch? File.for_fd File.foreach File.freeze File.frozen? File.ftype # File autocompletion just works >> File.file? '[TAB] .git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/ >> File.file? 'V[TAB] >> File.file? 'VERSION.yml'
Method Autocompletion
Autocompleting an object’s methods is Irb’s sweet spot. It achieves this by evaling the object you’ve typed and getting the methods it responds to via methods()
. Bond gives you access to your currently typed object and let’s you autocomplete as you see fit. For this example, let’s say we want to explore Bond::Agent
’s functionality:
>> ba = Bond.agent; nil => nil # Irb let's you autocomplete everything that an object responds to for better or worse. >> ba.[TAB] ba.__id__ ba.eql? ba.instance_eval ba.method ba.send ba.to_yaml ba.__send__ ba.equal? ba.instance_of? ba.methods ba.setup ba.to_yaml_properties ba.call ba.extend ba.instance_variable_defined? ba.missions ba.singleton_methods ba.to_yaml_style ba.class ba.find_mission ba.instance_variable_get ba.nil? ba.taguri ba.type ba.clone ba.freeze ba.instance_variable_set ba.object_id ba.taguri= ba.untaint ba.complete ba.frozen? ba.instance_variables ba.private_methods ba.taint ba.default_mission ba.hash ba.is_a? ba.protected_methods ba.tainted? ba.display ba.id ba.kind_of? ba.public_methods ba.to_a ba.dup ba.inspect ba.line_buffer ba.respond_to? ba.to_s # Since it's hard to see Bond::Agent's functionality amidst all the Object and Kernel methods, # let's autocomplete just it's instance methods. >> Bond.complete(:object=>Bond::Agent) {|input| input.object.class.instance_methods(false) } => true # A less cluttered display of Bond::Agent's functionality. >> ba.[TAB] ba.call ba.complete ba.default_mission ba.find_mission ba.missions # Let's have all Bond::* objects do this. >> Bond.reset; Bond.complete(:object=>/^Bond::/) {|input| input.object.class.instance_methods(false) } => true # Let's revert method autocompletion back to irb's defaults for Bond::* objects. >> Bond.recomplete :object=>/^Bond::/
Note that access in the completion proc to the current object you’ve typed is made available via input.object
.
Custom Searching of Completions
As we’ve seen Bond defaults to traditional autocompletion searching i.e. possible_completions.grep(/^#{input}/)
. But Bond can often be anything but traditional. Let’s revisit the first example:
# Let's have autocompletion search anywhere within possible completions, >> Bond.complete(:method=>'method1', :search=>false) {|input| %w{some args to autocomplete}.grep(/#{input}/) } => true # Crazy anywhere completion (not really) >> method1 'com[TAB] >> method1 'autocomplete' # Let's redefine the anywhere completion. This way we can reuse the anywhere search in other completions. >> anywhere = proc {|input, list| list.grep(/#{input}/) } => #< Proc:0x0053cdd4@(irb):3> >> Bond.recomplete(:method=>'method1', :search=>anywhere) {|input| %w{some args to autocomplete} } => true
# Firing up a rails console bash> script/console >> require 'bond' => true # Set all ActiveRecord::Base descendants to use the predefined underscore search >> Bond.complete :object=>ActiveRecord::Base, :search=>:underscore => true # With this search we can still autocomplete the traditional way. # Url is a model object >> Url.first.tag_[TAB] Url.first.tag_add_and_remove Url.first.tag_and_save Url.first.tag_ids= Url.first.tag_list= Url.first.tag_add_and_save Url.first.tag_ids Url.first.tag_list Url.first.tag_remove_and_save >> Url.tag_ad[TAB] >> Url.tag_add_and_ >> Url.tag_add_and_[TAB] Url.first.tag_add_and_remove Url.first.tag_add_and_save >> Url.tag_add_and_s[TAB] >> Url.tag_add_and_save # But this search goes the extra mile with textmate-like searching. # Type just the first letter of each underscored word separated by '_' >> Url.first.t_a_a_s[TAB] >> Url.first.tag_add_and_save # With this search, most multi-worded methods are just a few keystrokes away. # If multiple methods match the underscore alias, it still autocompletes the beginning of the method: >> Url.first.p[TAB] Url.first.partial_updates Url.first.pretty_inspect Url.first.pretty_print_instance_variables Url.first.public_methods Url.first.partial_updates? Url.first.pretty_print Url.first.primary_key_prefix_type Url.first.pluralize_table_names Url.first.pretty_print_cycle Url.first.private_methods Url.first.present? Url.first.pretty_print_inspect Url.first.protected_methods >> Url.first.p_p[TAB] >> Url.first.pretty_print >> Url.first.pretty_print_c[TAB] >> Url.first.pretty_print_cycle
As you can see, customizing the way we search possible completions brings a new twist to autocompletion.
Custom Autocompletions
So far we’ve seen how easy Bond makes autocompletion of methods and arguments. It’s easy because Bond handles the regular expressions. But if you have your own regex’s, Bond is ready:
bash> irb -rirb/completion -rubygems -rbond # Let's reuse the file completion from above >> file_completion = proc {|input| (Readline::FILENAME_COMPLETION_PROC.call(input) || []).map {|f| f =~ /^~/ ? File.expand_path(f) : f } } => #< Proc:0x0100f1d0@(irb):1> # But this time let's trigger it whenever the last word in the line is quoted # fyi this is default behavior if you use irb without requiring irb/completion >> Bond.complete(:on=>/\S+\s*["']([^'".]*)$/, :search=>false) {|input| file_completion.call(input.matched[1]) } => true # Now it doesn't matter what methods come before. If the last word is quoted we get file completion: >> Dir.entries '[TAB] .git/ LICENSE.txt README.rdoc Rakefile VERSION.yml bond.gemspec ext/ lib/ test/ >> Dir.entries 'l[TAB] >> Dir.entries 'lib/ >> `ls 't[TAB] >> `ls 'test/ >> `ls 'test/'` # String method completion still works >> '007'.[TAB] Display all 137 possibilities? (y or n)
Note the completion proc gets access to regex matches via input.matched
. This is the MatchData
object that results from matching the given regex with the current line. If you’re not familiar with a MatchData
object, the relevant thing to know is that you can access matches as you would array elements i.e. input.matched[1] == $1, input.matched[2] == $2
. If you look at the above regex for :on
, you’ll see that the first match is what we want to autocomplete and hence input.matched[1]
is passed in the completion proc.
With custom autocompletions you can do some surprisingly useful things. Here’s one to make typing known constants and classes oh so easy:
def alias_constants(input)
fetch_constants = proc {|klass, klass_alias| klass.constants.grep(/^#{klass_alias}/i).map {|f| klass.const_get(f)} }
fetch_string_constants = proc {|klass, klass_alias|
klass.constants.grep(/^#{klass_alias}/i).map {|f|
(val = klass.const_get(f)) && val.is_a?(Module) ? val.to_s : "#{klass}::#{f}"
}
}
index = 1
aliases = input.split(":")
aliases.inject([Object]) do |completions,a|
completions = completions.select {|e| e.is_a?(Module) }.map {|klass|
aliases.size != index ? fetch_constants.call(klass, a) : fetch_string_constants.call(klass, a)
}.flatten
index += 1; completions
end
end
This completion proc allows us to refer to any constants/classes by the first few unique letters of each namespace. The letters can be in lowercase and are separated by a colon. Let’s try it:
>> # Copy and paste the above method >> Bond.complete :on=>/^((([a-z][^:.\(]*)+):)+/, :search=>false, &method(:alias_constants) => true # First some easy ones >> b:a[TAB] >> Bond::Agent >> b:d:m[TAB] >> Bond::Missions::DefaultMission # If we hit multiple matches we just resort to normal completion >> i:i[TAB] >> IRB:I >> IRB:Il[TAB] >> IRB::IllegalParameter
The Name is Bond
To use Bond or have Bond in your irbrc, you only need to require it and then Bond.complete
to your Bond content. Remember that the order you define completions is the order they’re searched in. Be careful when using custom completions. Having something like Bond.complete :on=>/.*/, &mah_search
will swallow up any completions defined after it.
Although Bond has been shown in the context of Irb and Readline, Bond doesn’t need either of these. Heck, Bond doesn’t even need irb/completion when in irb. By debriefing Bond, Bond.debrief()
, it’s possible to set your own non-irb defaults and use other Readline-like libraries. However those Readline-like libraries will need a Bond plugin. Bond comes with plugins for both Rawline and of course Readline. For more about Bond, check out its homepage and docs.