diff options
author | Takashi Kokubun <[email protected]> | 2022-11-21 00:46:22 -0800 |
---|---|---|
committer | git <[email protected]> | 2022-11-21 08:46:27 +0000 |
commit | c9fbc779a680f3e1fd884ec80722cd32a990e0e9 (patch) | |
tree | eaecf5a5673cbd05b9ffb3747ce724323254edb5 /lib | |
parent | 65e31402ae46672e87cddb1f2e618d1c00591151 (diff) |
[ruby/irb] Add commands to start and use the debugger
(https://github.com/ruby/irb/pull/449)
* Seamlessly integrate a few debug commands
* Improve the break command support
* Utilize skip_src option if available
* Add step and delete commands
* Write end-to-end tests for each debugger command
* Add documentation
* Add backtrace, info, catch commands
https://github.com/ruby/irb/commit/976100c1c2
Diffstat (limited to 'lib')
-rw-r--r-- | lib/irb.rb | 6 | ||||
-rw-r--r-- | lib/irb/cmd/backtrace.rb | 21 | ||||
-rw-r--r-- | lib/irb/cmd/break.rb | 21 | ||||
-rw-r--r-- | lib/irb/cmd/catch.rb | 21 | ||||
-rw-r--r-- | lib/irb/cmd/continue.rb | 17 | ||||
-rw-r--r-- | lib/irb/cmd/debug.rb | 12 | ||||
-rw-r--r-- | lib/irb/cmd/delete.rb | 17 | ||||
-rw-r--r-- | lib/irb/cmd/finish.rb | 17 | ||||
-rw-r--r-- | lib/irb/cmd/info.rb | 31 | ||||
-rw-r--r-- | lib/irb/cmd/irb_info.rb | 34 | ||||
-rw-r--r-- | lib/irb/cmd/next.rb | 17 | ||||
-rw-r--r-- | lib/irb/cmd/step.rb | 18 | ||||
-rw-r--r-- | lib/irb/context.rb | 16 | ||||
-rw-r--r-- | lib/irb/extend-command.rb | 37 | ||||
-rw-r--r-- | lib/irb/init.rb | 5 | ||||
-rw-r--r-- | lib/irb/ruby-lex.rb | 4 |
16 files changed, 258 insertions, 36 deletions
diff --git a/lib/irb.rb b/lib/irb.rb index 0a856d3929..2db99bcd43 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -96,6 +96,8 @@ require_relative "irb/easter-egg" # * Show the source code around binding.irb again. # * debug # * Start the debugger of debug.gem. +# * break, delete, next, step, continue, finish, backtrace, info, catch +# * Start the debugger of debug.gem and run the command on it. # # == Configuration # @@ -470,10 +472,6 @@ module 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.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 0000000000..ac4f0e0e7e --- /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/break.rb b/lib/irb/cmd/break.rb new file mode 100644 index 0000000000..2c82413f6a --- /dev/null +++ b/lib/irb/cmd/break.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_relative "debug" + +module IRB + # :stopdoc: + + module ExtendCommand + class Break < Debug + def self.transform_args(args) + args&.dump + end + + def execute(args = nil) + super(pre_cmds: "break #{args}") + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/catch.rb b/lib/irb/cmd/catch.rb new file mode 100644 index 0000000000..8c9e086a9c --- /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/continue.rb b/lib/irb/cmd/continue.rb new file mode 100644 index 0000000000..94696e4b6c --- /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(do_cmds: ["continue", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index d43e060c67..9e2c096107 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -11,7 +11,7 @@ module IRB ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `irb'\z/ } IRB_DIR = File.expand_path('..', __dir__) - def execute(*args) + 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 @@ -25,11 +25,19 @@ module IRB return end + options = { oneshot: true, hook_call: false } + if 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) + DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) # exit current Irb#run call throw :IRB_EXIT end diff --git a/lib/irb/cmd/delete.rb b/lib/irb/cmd/delete.rb new file mode 100644 index 0000000000..3810ae414e --- /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/finish.rb b/lib/irb/cmd/finish.rb new file mode 100644 index 0000000000..de4b4f12cf --- /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(do_cmds: ["finish", *args].join(" ")) + end + end + end + + # :startdoc: +end diff --git a/lib/irb/cmd/info.rb b/lib/irb/cmd/info.rb index 37ecd762ff..413c286429 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 0000000000..8a4e1bd603 --- /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/cmd/next.rb b/lib/irb/cmd/next.rb new file mode 100644 index 0000000000..2943a753fb --- /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(do_cmds: ["next", *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 0000000000..dbd59806f8 --- /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/context.rb b/lib/irb/context.rb index c5d98772b8..91fbb2fcf1 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -486,9 +486,9 @@ module IRB @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 @@ -545,10 +545,16 @@ module IRB workspace.binding.local_variables end - # Return a command name if it's aliased from the argument and it's not an identifier. - def symbol_alias(command) + # 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[command.to_sym] + 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/extend-command.rb b/lib/irb/extend-command.rb index 7e120cf510..802c9aa6dc 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -125,12 +125,47 @@ module IRB # :nodoc: [:edit, NO_OVERRIDE], ], [ + :irb_break, :Break, "cmd/break", + ], + [ + :irb_catch, :Catch, "cmd/catch", + ], + [ + :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], + ], + [ + :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", [:help, NO_OVERRIDE], ], [ - :irb_info, :Info, "cmd/info" + :irb_info, :IrbInfo, "cmd/irb_info" ], [ diff --git a/lib/irb/init.rb b/lib/irb/init.rb index 831d7d811a..dd888f3727 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -160,8 +160,13 @@ module IRB # :nodoc: @CONF[:AT_EXIT] = [] @CONF[:COMMAND_ALIASES] = { + # Symbol aliases :'$' => :show_source, :'@' => :whereami, + # Keyword aliases + :break => :irb_break, + :catch => :irb_catch, + :next => :irb_next, } end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 28029bbf4c..85b336fbe1 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -65,9 +65,9 @@ class RubyLex 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 |