From 9ed9f415bef8d96af14fe97511c092fefda624cf Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 18 Nov 2022 10:43:41 -0800 Subject: [PATCH 1/7] Seamlessly integrate a few debug commands --- lib/irb.rb | 2 +- lib/irb/cmd/break.rb | 17 +++++++++++++++++ lib/irb/cmd/continue.rb | 17 +++++++++++++++++ lib/irb/cmd/debug.rb | 9 +++++++-- lib/irb/cmd/finish.rb | 17 +++++++++++++++++ lib/irb/cmd/next.rb | 17 +++++++++++++++++ lib/irb/context.rb | 8 ++++---- lib/irb/extend-command.rb | 15 +++++++++++++++ lib/irb/init.rb | 4 ++++ lib/irb/ruby-lex.rb | 2 +- 10 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 lib/irb/cmd/break.rb create mode 100644 lib/irb/cmd/continue.rb create mode 100644 lib/irb/cmd/finish.rb create mode 100644 lib/irb/cmd/next.rb diff --git a/lib/irb.rb b/lib/irb.rb index 64da85215..a8953b92e 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -427,7 +427,7 @@ def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) @context.main.extend ExtendCommandBundle @context.command_aliases.each do |alias_name, cmd_name| - next if @context.symbol_alias(alias_name) + next if @context.symbol_alias?(alias_name) || @context.main.respond_to?(alias_name) @context.main.install_alias_method(alias_name, cmd_name) end @signal_status = :IN_IRB diff --git a/lib/irb/cmd/break.rb b/lib/irb/cmd/break.rb new file mode 100644 index 000000000..e3035f4e9 --- /dev/null +++ b/lib/irb/cmd/break.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Break < Debug + def execute(*args) + super(['break', *args].join(' ')) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/continue.rb b/lib/irb/cmd/continue.rb new file mode 100644 index 000000000..05abe427c --- /dev/null +++ b/lib/irb/cmd/continue.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Continue < Debug + def execute(*args) + super(['continue', *args].join(' ')) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index d43e060c6..5a068c30c 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -11,7 +11,7 @@ class Debug < Nop ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } IRB_DIR = File.expand_path('..', __dir__) - def execute(*args) + def execute(debug_command = nil) unless binding_irb? puts "`debug` command is only available when IRB is started with binding.irb" return @@ -25,11 +25,16 @@ def execute(*args) return end + command = nil + if debug_command + command = ['irb', nil, debug_command] + 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) + DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, oneshot: true, hook_call: false, command: command) # exit current Irb#run call throw :IRB_EXIT end diff --git a/lib/irb/cmd/finish.rb b/lib/irb/cmd/finish.rb new file mode 100644 index 000000000..cfa87692d --- /dev/null +++ b/lib/irb/cmd/finish.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Finish < Debug + def execute(*args) + super(['finish', *args].join(' ')) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/next.rb b/lib/irb/cmd/next.rb new file mode 100644 index 000000000..7973f2755 --- /dev/null +++ b/lib/irb/cmd/next.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Next < Debug + def execute(*args) + super(['next', *args].join(' ')) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index c5d98772b..0eadc20e5 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -486,9 +486,9 @@ def evaluate(line, line_no, exception: nil) # :nodoc: @workspace.local_variable_set(:_, exception) end - # Transform a non-identifier alias (ex: @, $) + # Transform a non-identifier alias (@, $) or keywords (next, break) command, args = line.split(/\s/, 2) - if original = symbol_alias(command) + if original = command_aliases[command.to_sym] line = line.gsub(/\A#{Regexp.escape(command)}/, original.to_s) command = original end @@ -546,9 +546,9 @@ def local_variables # :nodoc: end # Return a command name if it's aliased from the argument and it's not an identifier. - def symbol_alias(command) + def symbol_alias?(command) return nil if command.match?(/\A\w+\z/) - command_aliases[command.to_sym] + command_aliases.key?(command.to_sym) end end end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 7da75fe14..5e8512995 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -120,6 +120,21 @@ def irb_context :irb_debug, :Debug, "cmd/debug", [:debug, NO_OVERRIDE], ], + [ + :irb_break, :Break, "cmd/break", + ], + [ + :irb_next, :Next, "cmd/next", + ], + [ + :irb_continue, :Continue, "cmd/continue", + [:continue, NO_OVERRIDE], + ], + [ + :irb_finish, :Finish, "cmd/finish", + [:finish, NO_OVERRIDE], + ], + [ :irb_help, :Help, "cmd/help", [:help, NO_OVERRIDE], diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 831d7d811..4ca06ba8e 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -160,8 +160,12 @@ def IRB.init_config(ap_path) @CONF[:AT_EXIT] = [] @CONF[:COMMAND_ALIASES] = { + # Symbol aliases :'$' => :show_source, :'@' => :whereami, + # Keyword aliases + :break => :irb_break, + :next => :irb_next, } end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 28029bbf4..f38007745 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -67,7 +67,7 @@ def set_input(io, p = nil, context:, &block) else # Accept any single-line input starting with a non-identifier alias (ex: @, $) command = code.split(/\s/, 2).first - if context.symbol_alias(command) + if context.symbol_alias?(command) next true end From e262fc277a0f6a614fa11857624fdd2d0915ef21 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 18 Nov 2022 21:26:06 -0800 Subject: [PATCH 2/7] Improve the break command support --- lib/irb/cmd/break.rb | 8 ++++++-- lib/irb/cmd/continue.rb | 2 +- lib/irb/cmd/debug.rb | 6 +++--- lib/irb/cmd/finish.rb | 2 +- lib/irb/cmd/next.rb | 2 +- lib/irb/context.rb | 8 +++++++- lib/irb/ruby-lex.rb | 4 ++-- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/irb/cmd/break.rb b/lib/irb/cmd/break.rb index e3035f4e9..2c82413f6 100644 --- a/lib/irb/cmd/break.rb +++ b/lib/irb/cmd/break.rb @@ -7,8 +7,12 @@ module IRB module ExtendCommand class Break < Debug - def execute(*args) - super(['break', *args].join(' ')) + def self.transform_args(args) + args&.dump + end + + def execute(args = nil) + super(pre_cmds: "break #{args}") end end end diff --git a/lib/irb/cmd/continue.rb b/lib/irb/cmd/continue.rb index 05abe427c..94696e4b6 100644 --- a/lib/irb/cmd/continue.rb +++ b/lib/irb/cmd/continue.rb @@ -8,7 +8,7 @@ module IRB module ExtendCommand class Continue < Debug def execute(*args) - super(['continue', *args].join(' ')) + super(do_cmds: ["continue", *args].join(" ")) end end end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index 5a068c30c..518d26d0f 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -11,7 +11,7 @@ class Debug < Nop ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } IRB_DIR = File.expand_path('..', __dir__) - def execute(debug_command = nil) + def execute(pre_cmds: nil, do_cmds: nil) unless binding_irb? puts "`debug` command is only available when IRB is started with binding.irb" return @@ -26,8 +26,8 @@ def execute(debug_command = nil) end command = nil - if debug_command - command = ['irb', nil, debug_command] + if pre_cmds || do_cmds + command = ['irb', pre_cmds, do_cmds] end # To make debugger commands like `next` or `continue` work without asking diff --git a/lib/irb/cmd/finish.rb b/lib/irb/cmd/finish.rb index cfa87692d..de4b4f12c 100644 --- a/lib/irb/cmd/finish.rb +++ b/lib/irb/cmd/finish.rb @@ -8,7 +8,7 @@ module IRB module ExtendCommand class Finish < Debug def execute(*args) - super(['finish', *args].join(' ')) + super(do_cmds: ["finish", *args].join(" ")) end end end diff --git a/lib/irb/cmd/next.rb b/lib/irb/cmd/next.rb index 7973f2755..2943a753f 100644 --- a/lib/irb/cmd/next.rb +++ b/lib/irb/cmd/next.rb @@ -8,7 +8,7 @@ module IRB module ExtendCommand class Next < Debug def execute(*args) - super(['next', *args].join(' ')) + super(do_cmds: ["next", *args].join(" ")) end end end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 0eadc20e5..91fbb2fcf 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -545,10 +545,16 @@ def local_variables # :nodoc: workspace.binding.local_variables end - # Return a command name if it's aliased from the argument and it's not an identifier. + # Return true if it's aliased from the argument and it's not an identifier. def symbol_alias?(command) return nil if command.match?(/\A\w+\z/) command_aliases.key?(command.to_sym) end + + # Return true if the command supports transforming args + def transform_args?(command) + command = command_aliases.fetch(command.to_sym, command) + ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) + end end end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index f38007745..85b336fbe 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -65,9 +65,9 @@ def set_input(io, p = nil, context:, &block) false end else - # Accept any single-line input starting with a non-identifier alias (ex: @, $) + # Accept any single-line input for symbol aliases or commands that transform args command = code.split(/\s/, 2).first - if context.symbol_alias?(command) + if context.symbol_alias?(command) || context.transform_args?(command) next true end From 2e46ec69cbd20a7859df16be86f30aafd289cd72 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 18 Nov 2022 22:01:24 -0800 Subject: [PATCH 3/7] Utilize skip_src option if available --- lib/irb/cmd/debug.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index 518d26d0f..9e2c09610 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -25,16 +25,19 @@ def execute(pre_cmds: nil, do_cmds: nil) return end - command = nil + options = { oneshot: true, hook_call: false } if pre_cmds || do_cmds - command = ['irb', pre_cmds, do_cmds] + options[:command] = ['irb', pre_cmds, do_cmds] + end + if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) + options[:skip_src] = true 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, command: command) + DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) # exit current Irb#run call throw :IRB_EXIT end From ca62e66be4475dd06aa013c294d82c9eea10bfde Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 18 Nov 2022 22:06:48 -0800 Subject: [PATCH 4/7] Add step and delete commands --- lib/irb/cmd/delete.rb | 17 +++++++++++++++++ lib/irb/cmd/step.rb | 18 ++++++++++++++++++ lib/irb/extend-command.rb | 8 ++++++++ 3 files changed, 43 insertions(+) create mode 100644 lib/irb/cmd/delete.rb create mode 100644 lib/irb/cmd/step.rb diff --git a/lib/irb/cmd/delete.rb b/lib/irb/cmd/delete.rb new file mode 100644 index 000000000..3810ae414 --- /dev/null +++ b/lib/irb/cmd/delete.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Delete < Debug + def execute(*args) + super(pre_cmds: ["delete", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/step.rb b/lib/irb/cmd/step.rb new file mode 100644 index 000000000..dbd59806f --- /dev/null +++ b/lib/irb/cmd/step.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Step < Debug + def execute(*args) + # Run `next` first to move out of binding.irb + super(pre_cmds: "next", do_cmds: ["step", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 5e8512995..c324bb050 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -126,6 +126,14 @@ def irb_context [ :irb_next, :Next, "cmd/next", ], + [ + :irb_delete, :Delete, "cmd/delete", + [:delete, NO_OVERRIDE], + ], + [ + :irb_step, :Step, "cmd/step", + [:step, NO_OVERRIDE], + ], [ :irb_continue, :Continue, "cmd/continue", [:continue, NO_OVERRIDE], From 67067a16461e2c67793c64ebbaf63166797d8ff7 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 18 Nov 2022 22:58:02 -0800 Subject: [PATCH 5/7] Write end-to-end tests for each debugger command --- Gemfile | 1 + test/irb/yamatanooroti/test_rendering.rb | 178 ++++++++++++++++++++++- 2 files changed, 178 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6f6d5419c..0b985d1e4 100644 --- a/Gemfile +++ b/Gemfile @@ -11,4 +11,5 @@ group :development do gem "stackprof" if is_unix && !is_truffleruby gem "test-unit" gem "reline", github: "ruby/reline" if ENV["WITH_LATEST_RELINE"] == "true" + gem "debug" end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index f9a130b7d..fe501574d 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -17,6 +17,8 @@ def setup @irbrc_backup = ENV['IRBRC'] @irbrc_file = ENV['IRBRC'] = File.join(@tmpdir, 'temporaty_irbrc') File.unlink(@irbrc_file) if File.exist?(@irbrc_file) + @ruby_file = File.join(@tmpdir, 'ruby_file.rb') + File.unlink(@ruby_file) if File.exist?(@ruby_file) end def teardown @@ -235,11 +237,185 @@ def test_assignment_expression_truncate EOC end - private def write_irbrc(content) + def test_debug + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + puts "Hello" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("debug\n") + write("next\n") + close + assert_include_screen(<<~EOC) + (rdbg) next # command + [1, 3] in #{@ruby_file} + 1| puts "start IRB" + 2| binding.irb + => 3| puts "Hello" + EOC + end + + def test_break + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + puts "Hello" + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("break 3\n") + write("continue\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) break 3 + #0 BP - Line #{@ruby_file}:3 (line) + EOC + assert_include_screen(<<~EOC) + (rdbg) continue # command + [1, 4] in #{@ruby_file} + 1| puts "start IRB" + 2| binding.irb + => 3| puts "Hello" + 4| puts "World" + =>#0
at #{@ruby_file}:3 + + Stop by #0 BP - Line #{@ruby_file}:3 (line) + EOC + end + + def test_delete + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + puts "Hello" + binding.irb + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("break 5\n") + write("continue\n") + write("delete 0\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) delete 0 + deleted: #0 BP - Line #{@ruby_file}:5 (line) + EOC + end + + def test_next + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + puts "Hello" + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("next\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) next + [1, 4] in #{@ruby_file} + 1| puts "start IRB" + 2| binding.irb + => 3| puts "Hello" + 4| puts "World" + =>#0
at #{@ruby_file}:3 + EOC + end + + def test_step + write_ruby <<~'RUBY' + puts "start IRB" + def foo + puts "Hello" + end + binding.irb + foo + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("step\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) step + [1, 7] in #{@ruby_file} + 1| puts "start IRB" + 2| def foo + => 3| puts "Hello" + 4| end + 5| binding.irb + EOC + end + + def test_continue + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + puts "Hello" + binding.irb + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("continue\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) continue + Hello + + From: #{@ruby_file} @ line 4 : + + 1: puts "start IRB" + 2: binding.irb + 3: puts "Hello" + => 4: binding.irb + 5: puts "World" + EOC + end + + def test_finish + write_ruby <<~'RUBY' + puts "start IRB" + def foo + binding.irb + puts "Hello" + end + foo + puts "World" + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("finish\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) finish + Hello + [1, 7] in #{@ruby_file} + 1| puts "start IRB" + 2| def foo + 3| binding.irb + 4| puts "Hello" + => 5| end + 6| foo + EOC + end + + private + + def assert_include_screen(expected) + assert_include(result.join("\n"), expected) + end + + def write_irbrc(content) File.open(@irbrc_file, 'w') do |f| f.write content end end + + def write_ruby(content) + File.open(@ruby_file, 'w') do |f| + f.write content + end + end end rescue LoadError, NameError # On Ruby repository, this test suit doesn't run because Ruby repo doesn't From 64c0bea4ef044083e4139bb61ba5a9935947bbc5 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 19 Nov 2022 14:31:21 -0800 Subject: [PATCH 6/7] Add documentation --- README.md | 2 ++ lib/irb.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 821d97ec2..56c3e9f36 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ The following commands are available on IRB. * Show the source code around binding.irb again. * `debug` * Start the debugger of debug.gem. +* `break`, `delete`, `next`, `step`, `continue`, `finish` + * Start the debugger of debug.gem and run the command on it. ## Documentation diff --git a/lib/irb.rb b/lib/irb.rb index b50847ab7..6ed7887d2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -96,6 +96,8 @@ # * Show the source code around binding.irb again. # * debug # * Start the debugger of debug.gem. +# * `break`, `delete`, `next`, `step`, `continue`, `finish` +# * Start the debugger of debug.gem and run the command on it. # # == Configuration # From c347b4e3a209b53eaf2988f7ff23d0be19dffd57 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Sat, 19 Nov 2022 14:36:51 -0800 Subject: [PATCH 7/7] Add backtrace, info, catch commands --- README.md | 2 +- lib/irb.rb | 6 +-- lib/irb/cmd/backtrace.rb | 21 ++++++++++ lib/irb/cmd/catch.rb | 21 ++++++++++ lib/irb/cmd/info.rb | 31 +++++---------- lib/irb/cmd/irb_info.rb | 34 ++++++++++++++++ lib/irb/extend-command.rb | 14 ++++++- lib/irb/init.rb | 1 + test/irb/yamatanooroti/test_rendering.rb | 49 ++++++++++++++++++++++++ 9 files changed, 150 insertions(+), 29 deletions(-) create mode 100644 lib/irb/cmd/backtrace.rb create mode 100644 lib/irb/cmd/catch.rb create mode 100644 lib/irb/cmd/irb_info.rb diff --git a/README.md b/README.md index 56c3e9f36..bde57edd5 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The following commands are available on IRB. * Show the source code around binding.irb again. * `debug` * Start the debugger of debug.gem. -* `break`, `delete`, `next`, `step`, `continue`, `finish` +* `break`, `delete`, `next`, `step`, `continue`, `finish`, `backtrace`, `info`, `catch` * Start the debugger of debug.gem and run the command on it. ## Documentation diff --git a/lib/irb.rb b/lib/irb.rb index 6ed7887d2..2db99bcd4 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -96,7 +96,7 @@ # * Show the source code around binding.irb again. # * debug # * Start the debugger of debug.gem. -# * `break`, `delete`, `next`, `step`, `continue`, `finish` +# * break, delete, next, step, continue, finish, backtrace, info, catch # * Start the debugger of debug.gem and run the command on it. # # == Configuration @@ -472,10 +472,6 @@ class Irb def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) @context.main.extend ExtendCommandBundle - @context.command_aliases.each do |alias_name, cmd_name| - next if @context.symbol_alias?(alias_name) || @context.main.respond_to?(alias_name) - @context.main.install_alias_method(alias_name, cmd_name) - end @signal_status = :IN_IRB @scanner = RubyLex.new end diff --git a/lib/irb/cmd/backtrace.rb b/lib/irb/cmd/backtrace.rb new file mode 100644 index 000000000..ac4f0e0e7 --- /dev/null +++ b/lib/irb/cmd/backtrace.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Backtrace < Debug + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["backtrace", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/catch.rb b/lib/irb/cmd/catch.rb new file mode 100644 index 000000000..8c9e086a9 --- /dev/null +++ b/lib/irb/cmd/catch.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Catch < Debug + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["catch", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb index 37ecd762f..413c28642 100644 --- a/lib/irb/cmd/info.rb +++ b/lib/irb/cmd/info.rb @@ -1,31 +1,18 @@ -# frozen_string_literal: false +# frozen_string_literal: true -require_relative "nop" +require_relative "debug" module IRB # :stopdoc: module ExtendCommand - class Info < Nop - def execute - Class.new { - def inspect - str = "Ruby version: #{RUBY_VERSION}\n" - str += "IRB version: #{IRB.version}\n" - str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" - str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file) - str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" - str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? - str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? - str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" - if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') - str += "Code page: #{codepage}\n" - end - str - end - alias_method :to_s, :inspect - }.new + class Info < Debug + def self.transform_args(args) + args&.dump + end + + def execute(*args) + super(pre_cmds: ["info", *args].join(" ")) end end end diff --git a/lib/irb/cmd/irb_info.rb b/lib/irb/cmd/irb_info.rb new file mode 100644 index 000000000..8a4e1bd60 --- /dev/null +++ b/lib/irb/cmd/irb_info.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: false + +require_relative "nop" + +module IRB + # :stopdoc: + + module ExtendCommand + class IrbInfo < Nop + def execute + Class.new { + def inspect + str = "Ruby version: #{RUBY_VERSION}\n" + str += "IRB version: #{IRB.version}\n" + str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" + str += ".irbrc path: #{IRB.rc_file}\n" if File.exist?(IRB.rc_file) + str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" + str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? + str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? + str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" + if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ + codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') + str += "Code page: #{codepage}\n" + end + str + end + alias_method :to_s, :inspect + }.new + end + end + end + + # :startdoc: +end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index f80c060d8..665e05a97 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -123,6 +123,9 @@ def irb_context [ :irb_break, :Break, "cmd/break", ], + [ + :irb_catch, :Catch, "cmd/catch", + ], [ :irb_next, :Next, "cmd/next", ], @@ -142,6 +145,15 @@ def irb_context :irb_finish, :Finish, "cmd/finish", [:finish, NO_OVERRIDE], ], + [ + :irb_backtrace, :Backtrace, "cmd/backtrace", + [:backtrace, NO_OVERRIDE], + [:bt, NO_OVERRIDE], + ], + [ + :irb_debug_info, :Info, "cmd/info", + [:info, NO_OVERRIDE], + ], [ :irb_help, :Help, "cmd/help", @@ -149,7 +161,7 @@ def irb_context ], [ - :irb_info, :Info, "cmd/info" + :irb_info, :IrbInfo, "cmd/irb_info" ], [ diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 4ca06ba8e..dd888f372 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -165,6 +165,7 @@ def IRB.init_config(ap_path) :'@' => :whereami, # Keyword aliases :break => :irb_break, + :catch => :irb_catch, :next => :irb_next, } end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index fe501574d..485fa47c2 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -399,6 +399,55 @@ def foo EOC end + def test_backtrace + write_ruby <<~'RUBY' + puts "start IRB" + def foo + binding.irb + end + foo + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("backtrace\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) backtrace + =>#0 Object#foo at #{@ruby_file}:3 + #1
at #{@ruby_file}:5 + EOC + end + + def test_info + write_ruby <<~'RUBY' + puts "start IRB" + a = 1 + binding.irb + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("info\n") + close + assert_include_screen(<<~EOC) + (rdbg:irb) info + %self = main + a = 1 + EOC + end + + def test_catch + write_ruby <<~'RUBY' + puts "start IRB" + binding.irb + raise NotImplementedError + RUBY + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@ruby_file}}, startup_message: 'start IRB') + write("catch NotImplementedError\n") + write("continue\n") + close + assert_include_screen(<<~EOC) + Stop by #0 BP - Catch "NotImplementedError" + EOC + end + private def assert_include_screen(expected)