Changing Readline Completions With a Key Combo
Readline has the powerful concept of a macro which maps a key combo to anything you can type. We’ll look at using a readline macro to trigger ruby code in a ruby shell. Since ruby has the gem bond to easily define readline completions, we’ll change completion behavior with the flick of a key combo.
Intro
First off, you should be aware that what follows can be done with any readline, non-ruby completion setup that allows painless redefinition of a completion.
Defining and redefining completions are easy in ruby with bond :
bash> irb -rubygems -rbond >> Bond.complete(:method=>'shoot') { %w{No Big Goldfinger} } => true >> shoot 'G[TAB] >> shoot 'Goldfinger' # Redefine completion with same condition (:method=>'shoot') but different completion behavior >> Bond.recomplete(:method=>'shoot', :search=>:anywhere) { %w{Hammerstein Grubozaboyschikov Spang} } => true >> shoot 'b[TAB] >> shoot 'Grubozaboyschikov'
Readline Macros
If you use the console enough, readline is your friend. You’ve probably read the docs and made your own ~/.inputrc
. What we care about here is readline’s macros. With a macro, you can map a keystroke combo to any number of keystrokes. These mapped keystrokes can even include keystrokes that trigger other readline macros or that hit enter to execute something in a shell! Just for fun, let’s execute some ruby code in irb with a keystroke combo. Drop this in your ~/.inputrc:
# Only defined when using ruby's readline library i.e. in irb $if Ruby "\C-x\C-p": "puts \"So many keystrokes. So little time.\"\n" $endif
Start irb and press Control-X Control-P:
bash> irb >> [C-xC-p] >> puts "So many keystrokes. So little time." So many keystrokes. So little time. => nil
Ruby code executed at the flick of a key combo!
Ruby Readline Example
I like method completion to autocomplete any method an object responds to … most of the time. But if I’m using a new ruby gem and it’s unfamiliar objects, I usually want to try out its functionality. Irb’s default method completion makes this painful since completion gives you all the methods an objects responds to. Sure, I can call self.class.instance_methods(false)
on an object but why do that every time when I can just change an object’s completion behavior with a key combo? Here’s the following method I use to toggle object completion for bond/completion :
def toggle_object_complete
# default mode
if @object_complete
Bond.recomplete(:object=>'Object', :place=>:last)
Bond.recomplete(:object=>'Object', :on=>/([^.\s]+)\.([^.\s]*)$/, :place=>:last)
else
non_inherited_methods = proc {|e|
e.object.is_a?(Module) ? e.object.methods(false) : e.object.class.instance_methods(false)
}
Bond.recomplete(:object=>'Object', :place=>:last, &non_inherited_methods)
Bond.recomplete(:object=>'Object', :on=>/([^.\s]+)\.([^.\s]*)$/, :place=>:last, &non_inherited_methods)
end
@object_complete = !@object_complete
end
This method checks the current toggle state and switches between the two completion definitions per call. Notice that two completions are redefined since bond/completion uses two completion definitions to control an object’s method completion.
Now let’s map this to method to a key combo in ~/.inputrc:
$if Ruby "\C-x\C-p": "toggle_object_complete\n" $endif
Let’s try this key combo in the console:
>> # copy and paste the toggle_object_complete method # Default method completion >> Bond.agent.[TAB] Bond.agent.__id__ Bond.agent.freeze Bond.agent.iv Bond.agent.private_methods Bond.agent.taint Bond.agent.__send__ Bond.agent.frozen? Bond.agent.kind_of? Bond.agent.protected_methods Bond.agent.tainted? Bond.agent.call Bond.agent.giv Bond.agent.line_buffer Bond.agent.public_methods Bond.agent.to_a Bond.agent.class Bond.agent.grep Bond.agent.local_methods Bond.agent.recomplete Bond.agent.to_s Bond.agent.clone Bond.agent.hash Bond.agent.ls Bond.agent.reset Bond.agent.to_yaml Bond.agent.complete Bond.agent.id Bond.agent.metaclass Bond.agent.respond_to? Bond.agent.to_yaml_properties Bond.agent.create_mission Bond.agent.inspect Bond.agent.method Bond.agent.send Bond.agent.to_yaml_style Bond.agent.default_mission Bond.agent.instance_eval Bond.agent.methods Bond.agent.setup Bond.agent.type Bond.agent.display Bond.agent.instance_of? Bond.agent.mgrep Bond.agent.singleton_methods Bond.agent.untaint Bond.agent.dup Bond.agent.instance_variable_defined? Bond.agent.missions Bond.agent.siv Bond.agent.which Bond.agent.eql? Bond.agent.instance_variable_get Bond.agent.mls Bond.agent.sort_last_missions Bond.agent.equal? Bond.agent.instance_variable_set Bond.agent.mwhich Bond.agent.spy Bond.agent.extend Bond.agent.instance_variables Bond.agent.nil? Bond.agent.taguri Bond.agent.find_mission Bond.agent.is_a? Bond.agent.object_id Bond.agent.taguri= # Let's use our key combo to just see what's relevant. >> [C-xC-p] >> toggle_object_complete => true >> Bond.agent.[TAB] Bond.agent.call Bond.agent.create_mission Bond.agent.find_mission Bond.agent.recomplete Bond.agent.sort_last_missions Bond.agent.complete Bond.agent.default_mission Bond.agent.missions Bond.agent.reset Bond.agent.spy >> Bond.agent.missions[0].[TAB] Bond.agent.missions[0].method_condition Bond.agent.missions[0].set_input Bond.agent.missions[0].unique_id # When we want to change back to default method completion >> [C-xC-p] >> toggle_object_complete => false
Sweet. With a simple key combo, we can now alter method completion for any ruby object! One shortcoming you can see is that the key combo must be executed when in a blank line. Otherwise you’ll end up executing what’s already in the line as well. It’s possible to bypass this if anyone ever gets around to writing a Ruby binding for setting readline’s line buffer with rl_replace_line()
. If I ever get around to brushing up on C one of these days…
Outro
There are two cool ideas I hope you got from this. First, changing completion behavior with a key combo isn’t limited to a ruby shell. With bash, you could map a key combo to call a bash function that redefines completions with bash’s complete()
. Second, when in a ruby shell, you can execute any ruby code with a simple key combo. Although I only looked at changing completion behavior with key combos, there are an endless number of possibilities to be played with. I’m curious to see what others will come up with. If you’re hungering for more readline configuration tips, check out my inputrc.