summaryrefslogtreecommitdiff
path: root/lib/irb.rb
diff options
context:
space:
mode:
authorStan Lo <[email protected]>2023-08-13 19:30:30 +0100
committergit <[email protected]>2023-08-13 18:30:34 +0000
commit7f8f62c93bf3d11a0321fa91823065a2ff36f6d0 (patch)
tree2dffe13305f50883f33644f9d701ebb832ec0ab4 /lib/irb.rb
parent9099d62ac77cdca548bc4110e2cb03057ef0ac8f (diff)
[ruby/irb] Support seamless integration with ruby/debug
(https://github.com/ruby/irb/pull/575) * Support native integration with ruby/debug * Prevent using multi-irb and activating debugger at the same time Multi-irb makes a few assumptions: - IRB will manage all threads that host sub-irb sessions - All IRB sessions will be run on the threads created by IRB itself However, when using the debugger these assumptions are broken: - `debug` will freeze ALL threads when it suspends the session (e.g. when hitting a breakpoint, or performing step-debugging). - Since the irb-debug integration runs IRB as the debugger's interface, it will be run on the debugger's thread, which is not managed by IRB. So we should prevent the 2 features from being used at the same time. To do that, we check if the other feature is already activated when executing the commands that would activate the other feature. https://github.com/ruby/irb/commit/d8fb3246be
Diffstat (limited to 'lib/irb.rb')
-rw-r--r--lib/irb.rb83
1 files changed, 76 insertions, 7 deletions
diff --git a/lib/irb.rb b/lib/irb.rb
index c3631715da..c884d70a67 100644
--- a/lib/irb.rb
+++ b/lib/irb.rb
@@ -18,6 +18,7 @@ require_relative "irb/color"
require_relative "irb/version"
require_relative "irb/easter-egg"
+require_relative "irb/debug"
# IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby
# expressions read from the standard input.
@@ -373,8 +374,6 @@ module IRB
class Abort < Exception;end
@CONF = {}
-
-
# Displays current configuration.
#
# Modifying the configuration is achieved by sending a message to IRB.conf.
@@ -441,7 +440,7 @@ module IRB
# Creates a new irb session
def initialize(workspace = nil, input_method = nil)
@context = Context.new(self, workspace, input_method)
- @context.main.extend ExtendCommandBundle
+ @context.workspace.load_commands_to_main
@signal_status = :IN_IRB
@scanner = RubyLex.new(@context)
end
@@ -457,6 +456,38 @@ module IRB
end
end
+ def debug_readline(binding)
+ workspace = IRB::WorkSpace.new(binding)
+ context.workspace = workspace
+ context.workspace.load_commands_to_main
+ scanner.increase_line_no(1)
+
+ # When users run:
+ # 1. Debugging commands, like `step 2`
+ # 2. Any input that's not irb-command, like `foo = 123`
+ #
+ # Irb#eval_input will simply return the input, and we need to pass it to the debugger.
+ input = if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving?
+ # Previous IRB session's history has been saved when `Irb#run` is exited
+ # We need to make sure the saved history is not saved again by reseting the counter
+ context.io.reset_history_counter
+
+ begin
+ eval_input
+ ensure
+ context.io.save_history
+ end
+ else
+ eval_input
+ end
+
+ if input&.include?("\n")
+ scanner.increase_line_no(input.count("\n") - 1)
+ end
+
+ input
+ end
+
def run(conf = IRB.conf)
in_nested_session = !!conf[:MAIN_CONTEXT]
conf[:IRB_RC].call(context) if conf[:IRB_RC]
@@ -542,6 +573,18 @@ module IRB
@scanner.each_top_level_statement do |line, line_no, is_assignment|
signal_status(:IN_EVAL) do
begin
+ # If the integration with debugger is activated, we need to handle certain input differently
+ if @context.with_debugger
+ command_class = load_command_class(line)
+ # First, let's pass debugging command's input to debugger
+ # Secondly, we need to let debugger evaluate non-command input
+ # Otherwise, the expression will be evaluated in the debugger's main session thread
+ # This is the only way to run the user's program in the expected thread
+ if !command_class || ExtendCommand::DebugCommand > command_class
+ return line
+ end
+ end
+
evaluate_line(line, line_no)
# Don't echo if the line ends with a semicolon
@@ -633,6 +676,12 @@ module IRB
@context.evaluate(line, line_no)
end
+ def load_command_class(line)
+ command, _ = line.split(/\s/, 2)
+ command_name = @context.command_aliases[command.to_sym]
+ ExtendCommandBundle.load_command(command_name || command)
+ end
+
def convert_invalid_byte_sequence(str, enc)
str.force_encoding(enc)
str.scrub { |c|
@@ -986,12 +1035,32 @@ class Binding
#
# See IRB@Usage for more information.
def irb(show_code: true)
+ # Setup IRB with the current file's path and no command line arguments
IRB.setup(source_location[0], argv: [])
+ # Create a new workspace using the current binding
workspace = IRB::WorkSpace.new(self)
+ # Print the code around the binding if show_code is true
STDOUT.print(workspace.code_around_binding) if show_code
- binding_irb = IRB::Irb.new(workspace)
- binding_irb.context.irb_path = File.expand_path(source_location[0])
- binding_irb.run(IRB.conf)
- binding_irb.debug_break
+ # Get the original IRB instance
+ debugger_irb = IRB.instance_variable_get(:@debugger_irb)
+
+ irb_path = File.expand_path(source_location[0])
+
+ if debugger_irb
+ # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance
+ debugger_irb.context.workspace = workspace
+ debugger_irb.context.irb_path = irb_path
+ # If we've started a debugger session and hit another binding.irb, we don't want to start an IRB session
+ # instead, we want to resume the irb:rdbg session.
+ IRB::Debug.setup(debugger_irb)
+ IRB::Debug.insert_debug_break
+ debugger_irb.debug_break
+ else
+ # If we're not in a debugger session, create a new IRB instance with the current workspace
+ binding_irb = IRB::Irb.new(workspace)
+ binding_irb.context.irb_path = irb_path
+ binding_irb.run(IRB.conf)
+ binding_irb.debug_break
+ end
end
end