In this blog post, we will delve into the major enhancements introduced in Ruby 3.3’s IRB, as well as what’s currently planned for the next year.

In a nutshell, Ruby 3.3’s IRB offers improved debugging capabilities, an enhanced autocompletion experience, and an overall improved user experience.

It’s worth noting that even if you don’t upgrade to Ruby 3.3 immediately, you can still install the same version of IRB (v1.11.0) in any Ruby 2.7+ projects by adding gem "irb" to its Gemfile.

Table Of Contents

Advancing Debugging with IRB

In Ruby 3.2’s IRB (v1.6), we introduced a set of shortcut commands such as debug and next that allowed for a swift transition to the debug gem’s session from a binding.irb breakpoint. (To avoid confusion, I’ll refer to the debug gem’s session as rdbg from here on.)

This was a significant improvement. However, with the switch, users would instantly lose certain IRB features, such as multi-line input:

irb(main):001:0> debug
(ruby) if true
eval error: (rdbg)/test.rb:1: syntax error, unexpected end-of-input, expecting `then' or ';' or '\n'
if true
       ^
nil
(rdbg)

This year, we’ve taken the user experience to a new level by introducing the irb:rdbg session, which allows users to execute all rdbg commands without exiting the IRB session.

irb(main):001> debug
irb:rdbg(main):002* if true
irb:rdbg(main):003*   puts "Multi-line input and more!"
irb:rdbg(main):004> end
Multi-line input and more!
nil
irb:rdbg(main):005> th
# `th` (show all threads) is a example of a command only present in a `rdbg` session
--> #0 (sleep)@test.rb:2:in `<main>'
irb:rdbg(main):006>

This deep integration offers several advantages:

But most importantly, it offers users a debugging experience equivalent to pry-byebug.

Accessing irb:rdbg from binding.break / debugger

With the debug gem v1.9.0, which will also be part of Ruby 3.3, you can configure it to run irb:rdbg either by:

  • Setting its irb_console config through the RUBY_DEBUG_IRB_CONSOLE=1 environment variable
  • Running the irb command in a rdbg session

Doing so will give you the same irb:rdbg session in your debug console:

$ RUBY_DEBUG_IRB_CONSOLE=1 bundle exec rdbg test.rb
[1, 1] in test.rb
=>   1| a = 1
=>#0    <main> at test.rb:1
irb:rdbg(main):002>

Enhancing Autocompletion

Addressing Completion Dropdown Issues

In Ruby 3.1, IRB introduced the autocompletion feature. While it has proven to be a useful feature for many users, it also came with some issues that were hard to overlook:

  • The dropdown could push up the prompt when the candidate list is long. This not only distracts users but also pushes up the output history, forcing users to frequently scroll up and down through the session.
  • The dropdown’s colors are not configurable, making text hard to read with some terminal themes.
  • Under certain conditions, the dropdown could erase previously rendered content.

Here’s a short demonstration of these issues:

autocompletion issues demo

However, thanks to several fixes on the Reline gem (a pure-Ruby replacement of readline), these issues have been addressed.

improved autocompletion demo

We are aware that there are other existing issues, such as the lack of debouncing or tab-completion options. And we will keep improving the feature in future releases.

Customizing Dropdown UI with Reline::Face

With Reline v0.4.0+, you can use the Reline::Face class to customize the dropdown UI’s style in your .irbrc.

For instance, this snippet will change the dropdown from its default color scheme to a black-and-white theme:

Reline::Face.config(:completion_dialog) do |conf|
  conf.define :default, foreground: :white, background: :black
  conf.define :enhanced, foreground: :black, background: :white
  conf.define :scrollbar, foreground: :white, background: :black
end

The result can be seen below:

left: default, right: with the customization

Experimenting with More Accurate Completion Using RBS

Currently, IRB’s autocompletion is powered by a combination of regular expressions and runtime information from Binding.

This provides a decent result for the first level of completion, but can’t support chained method calls.

For example, when the user types 'Ruby'.up, IRB can list upcase and upcase!, because it knows 'Ruby' is a String.

But when the user types 'Ruby'.upcase., IRB can’t make another suggestion. This is because it cannot evaluate 'Ruby'.upcase to get its value as it risks causing side-effects (for example, calling User.all. in a Rails app).

However, there is another way we can get an object’s information without evaluation in Ruby: gradual typing. Therefore, this year @tompng initiated the repl_type_completor project to explore if it’s a feasible replacement for the current regexp-based completor.

To give it a try, you need to add this to your Gemfile:

gem "repl_type_completor", group: [:development, :test]

And to activate it:

  • Pass the --type-completor flag when starting IRB
  • Or add this to your irbrc file:
IRB.conf[:COMPLETOR] = :type # default is :regexp

For more details, please refer to IRB readme’s type completion section.

Quality of Life Improvements

Pager Support

This year, IRB has introduced pager support on the output of:

  • The show_source, ls, and show_cmds commands
  • The new history command
  • Evaluation result

When these outputs’ height exceeds your terminal’s, IRB will now paginate them:

pager demo

This offers several benefits:

  • You can freely scroll up and down with your arrow-up/down keys
  • You won’t overscroll to some previous IRB output
  • By typing /[search_term], like /foo, inside a pager, you can easily search any text that contains foo
    • This could be extremely useful when inspecting record(s) in a Rails console

We understand that for some users or under certain conditions, a pager could be more of a hindrance than a help. Therefore, it’s also possible to disable it with the --no-pager flag, or by adding this to your irbrc file:

IRB.conf[:USE_PAGER] = false

Omitting Return Value Inspection with ;

Even though paging evaluation result should make long output easier to inspect, sometimes we simply don’t want to see the return value of certain expressions. For example, the result of users = User.all when it returns hundreds of records.

In such cases, the new IRB allows users to omit the return value by adding a ; at the end, like users = User.all;.

Example

irb(main):001> long_string = "foo" * 10000;
irb(main):002> long_string.size
=> 30000

History Command

IRB now has a history command to display all stored input history (which by default is limited to 1000).

Since we usually have a long input history, this command also comes with a -g [term] flag for filtering.

irb(main):006> history -g self
1000: history -g self
803: self
769: self
731: watch self @foo
698: break self.inspect
584: self
582: self

Streamlined Prompt

As you may have noticed, IRB’s prompt is now a bit shorter:

Before

irb(main):001:0>

After

irb(main):001>

The removed part is the indent level number, which became redundant after IRB implemented a robust auto-indent feature for multi-line input.

Enhanced show_source Command

The show_source command has always been an essential tool for many IRB users, especially for debugging. This year it received two enhancements that will make it even more useful:

  • You can now use -s to get the method’s super definition if it has one
  • It can now display private methods too

The -s Flag

Given this script:

class Foo
  def foo
    "foo"
  end
end

class Bar < Foo
  def foo
    super + "bar"
  end
end

You can now get both Bar#foo and its super method (Foo#foo)’s definition:

irb(main):001> show_source Bar#foo

From: test.rb:8

  def foo
    super + "bar"
  end

=> nil
irb(main):002> show_source Bar#foo -s

From: test.rb:2

  def foo
    "foo"
  end

=> nil

You can also stack the flag, like -ss, to walk up the super method chain. And if IRB can’t find the super definition anymore, it’ll notify you with:

irb(main):003> show_source Bar#foo -ss
Error: Couldn't locate a super definition for Bar#foo
=> nil

What’s on the Horizon for Ruby 3.4?

The help Command Will Print Help Message

When we use a terminal-based application, the first thing we normally do is to type help and learn how to use it.

However, due to historical reasons, IRB’s help command opens up an ri input for Ruby document lookup instead. This unconventional design can make it hard, especially for new Ruby developers, to learn IRB’s usage. So we decided to change it in several steps:

  1. In Ruby 3.2, we added show_cmds to print the help message and show_doc command as an alias to the help command.
  2. In Ruby 3.3, IRB will warn users that help is going to be repurposed to act as show_cmds.
  3. In early 2024, we plan to release IRB v2.0, with the repurposing of help command being one of the breaking changes.

Enhancing Extensibility: Command and Helper Method

Several major Ruby web frameworks, such as Rails and Hanami, utilize IRB as a platform for their consoles. Additionally, other libraries extend IRB with their custom features via commands.

However, until now, there have been no standard APIs on the IRB side to accommodate new commands or helper methods. This situation leads to several challenges:

  • IRB is unable to display these extended features in its help message.
  • Refactoring the relevant parts becomes challenging because they are essentially public when private APIs are used directly.
  • Projects need to devise their own ways to utilize IRB, often resulting in similar but slightly different solutions.

To address these issues, we plan to provide official APIs and relevant documentation to help libraries and applications extend IRB. Our goal is to transform IRB from not just a great tool, but also into a great platform for other tools.

Acknowledgements

The remarkable progress we see on IRB was made possible by the IRB/Reline maintainers team:

And also, a big thank you to all the community contributors.