Blog / IRB completion from the debugger

Tom ten Thij
August 18, 2010

The default IRB completion code that ships with Ruby does not work well when being invoked from the debugger prompt:


>: ruby /test_script.rb
[-2, 7] in /test_script.rb
   1  require 'ruby-debug'
   2  debugger
=> 3  :foo
/test_script.rb:3
:foo
(rdb:1) INTERNAL ERROR!!! undefined method `workspace' for nil:NilClass
        /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/irb/completion.rb:38
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/interface.rb:112:in `call'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/interface.rb:112:in `readline'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/interface.rb:112:in `readline'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/interface.rb:62:in `read_command'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/processor.rb:246:in `process_commands'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-0.10.3/cli/ruby-debug/processor.rb:171:in `__at_line'
        (eval):5:in `at_line'
        (eval):3:in `synchronize'
        (eval):3:in `at_line'
        /Users/tomtt/.gem/ruby/1.8/gems/ruby-debug-base-0.10.3/lib/ruby-debug-base.rb:54:in `at_line'
        /test_script.rb:3

The reason why this happens is that the completion code assumes that IRB.conf[:MAIN_CONTEXT] is defined, but this is not the case when debugger is used. All we are really after is a valid binding, so to patch we can just use self.binding when the irb context is not available. An example patch can be found on my fork of an unofficial ruby repository.

In these days where rvm allows multiple ruby versions to be used, patching is not a patch-once-and-forget issue any longer. Therefore I felt the need to write a utility for this. It checks if your current version of ruby is patched and if not, shows the filename that needs to be updated and the suggest code change. It is bundled as the patch_irb_completion gem: simply “gem install patch_irb_completion;patch_irb_completion”. But that gives the overhead of having to install a gem for every ruby version. So maybe an executable script is a better solution. Here is one:


#!/usr/bin/env ruby

require 'find'

module PatchIRBCompletion
  class Suggest
    def self.find_ruby_lib_path
      ruby_bin_path = `which ruby`
      ruby_path = File.expand_path(File.join(ruby_bin_path, "..", "..", "lib"))
      unless File.exist?(ruby_path)
        ruby_path = `ruby -e "puts $:[0]"`
      end
    end

    def self.find_completion_source_in_dir(dir)
      Find.find(dir) do |filename|
        # puts filename
        next unless File.basename(filename) == "completion.rb"
        return filename
      end
      nil
    end

    def self.find_completion_source_in_current_ruby_version
      $:.each do |lib_dir|
        completion_file = find_completion_source_in_dir(lib_dir)
        next unless completion_file
        return completion_file
      end
    end

    def self.check_if_completion_source_file_has_offending_code(filename)
      unpatched_code = "IRB.conf[:MAIN_CONTEXT].workspace"
      File.read(filename).include?(unpatched_code)
    end

    def self.call
      filename = find_completion_source_in_current_ruby_version
      if check_if_completion_source_file_has_offending_code(filename)
        puts <

Here is some example output:


>: patch_irb_completion
The file "/Users/tomtt/.rvm/rubies/ruby-1.9.2-rc2/lib/ruby/1.9.1/irb/completion.rb" has not been patched... I would suggest you replace:
    bind = IRB.conf[:MAIN_CONTEXT].workspace.binding (line 38)
with:
    context = IRB.conf[:MAIN_CONTEXT]
    bind = context ? context.workspace.binding : binding