diff options
author | Takashi Kokubun <[email protected]> | 2022-11-18 09:34:38 -0800 |
---|---|---|
committer | git <[email protected]> | 2022-11-18 17:34:42 +0000 |
commit | b1cbc883f2add06479113b61005f4cdfa90ff266 (patch) | |
tree | 6c21d94e81911b3d25387cb585c37cabe09ab026 | |
parent | 10788166e7e568fdcd0b748e8d5dab442dcdc7ef (diff) |
[ruby/irb] Minor fixes on debug command
(https://github.com/ruby/irb/pull/447)
* Minor fixes on debug command
* Update lib/irb/cmd/debug.rb
-rw-r--r-- | lib/irb.rb | 9 | ||||
-rw-r--r-- | lib/irb/cmd/debug.rb | 70 |
2 files changed, 59 insertions, 20 deletions
diff --git a/lib/irb.rb b/lib/irb.rb index 1cd89d295c..64da852157 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -393,8 +393,6 @@ module IRB end class Irb - DIR_NAME = __dir__ - ASSIGNMENT_NODE_TYPES = [ # Local, instance, global, class, constant, instance, and index assignment: # "foo = bar", @@ -436,13 +434,14 @@ module IRB @scanner = RubyLex.new end + # A hook point for `debug` command's TracePoint after :IRB_EXIT as well as its clean-up def debug_break # it means the debug command is executed - if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:original_capture_frames) + if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) # after leaving this initial breakpoint, revert the capture_frames patch - DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :original_capture_frames) + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb) # and remove the redundant method - DEBUGGER__.singleton_class.send(:undef_method, :original_capture_frames) + DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb) end end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index 8aab40cf84..369c112257 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -5,28 +5,68 @@ module IRB module ExtendCommand class Debug < Nop + BINDING_IRB_FRAME_REGEXPS = [ + '<internal:prelude>', + binding.method(:irb).source_location.first, + ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } + IRB_DIR = File.expand_path('..', __dir__) + def execute(*args) - require "debug/session" - DEBUGGER__.start(nonstop: true) - DEBUGGER__.singleton_class.send(:alias_method, :original_capture_frames, :capture_frames) - - def DEBUGGER__.capture_frames(skip_path_prefix) - frames = original_capture_frames(skip_path_prefix) - frames.reject! do |frame| - frame.realpath&.start_with?(::IRB::Irb::DIR_NAME) || frame.path.match?(/internal:prelude/) - end - frames + unless binding_irb? + puts "`debug` command is only available when IRB is started with binding.irb" + return end + unless setup_debugger + puts <<~MSG + You need to install the debug gem before using this command. + If you use `bundle exec`, please add `gem "debug"` into your Gemfile. + MSG + return + end + + # To make debugger commands like `next` or `continue` work without asking + # the user to quit IRB after that, we need to exit IRB first and then hit + # a TracePoint on #debug_break. file, lineno = IRB::Irb.instance_method(:debug_break).source_location DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, oneshot: true, hook_call: false) # exit current Irb#run call throw :IRB_EXIT - rescue LoadError => e - puts <<~MSG - You need to install the debug gem before using this command. - If you use `bundle exec`, please add `gem "debug"` into your Gemfile. - MSG + end + + private + + def binding_irb? + caller.any? do |frame| + BINDING_IRB_FRAME_REGEXPS.any? do |regexp| + frame.match?(regexp) + end + end + end + + def setup_debugger + unless defined?(DEBUGGER__::SESSION) + begin + require "debug/session" + rescue LoadError + return false + end + DEBUGGER__.start(nonstop: true) + end + + unless DEBUGGER__.respond_to?(:capture_frames_without_irb) + DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) + + def DEBUGGER__.capture_frames(*args) + frames = capture_frames_without_irb(*args) + frames.reject! do |frame| + frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>" + end + frames + end + end + + true end end end |