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.
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:
@
(whereami
) and $
(show_source
)But most importantly, it offers users a debugging experience equivalent to pry-byebug
.
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:
irb_console
config through the RUBY_DEBUG_IRB_CONSOLE=1
environment variableirb
command in a rdbg
sessionDoing 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>
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:
Here’s a short demonstration of these issues:
However, thanks to several fixes on the Reline gem (a pure-Ruby replacement of readline
),
these issues have been addressed.
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.
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:
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:
--type-completor
flag when starting IRBirbrc
file:IRB.conf[:COMPLETOR] = :type # default is :regexp
For more details, please refer to IRB readme’s type completion section.
This year, IRB has introduced pager support on the output of:
show_source
, ls
, and show_cmds
commandshistory
commandWhen these outputs’ height exceeds your terminal’s, IRB will now paginate them:
This offers several benefits:
/[search_term]
, like /foo
, inside a pager, you can easily search any text that contains foo
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
;
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;
.
irb(main):001> long_string = "foo" * 10000;
irb(main):002> long_string.size
=> 30000
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
As you may have noticed, IRB’s prompt is now a bit shorter:
irb(main):001:0>
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.
show_source
CommandThe 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:
-s
to get the method’s super definition if it has one-s
FlagGiven 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
help
Command Will Print Help MessageWhen 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:
show_cmds
to print the help message and show_doc
command as an alias to the help
command.help
is going to be repurposed to act as show_cmds
.v2.0
, with the repurposing of help
command being one of the breaking changes.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:
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.
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.