Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands to start and use the debugger #449

Merged
merged 9 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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`, `backtrace`, `info`, `catch`
* Start the debugger of debug.gem and run the command on it.

## Documentation

Expand Down
6 changes: 2 additions & 4 deletions lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, backtrace, info, catch
# * Start the debugger of debug.gem and run the command on it.
#
# == Configuration
#
Expand Down Expand Up @@ -470,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.install_alias_method(alias_name, cmd_name)
end
@signal_status = :IN_IRB
@scanner = RubyLex.new
end
Expand Down
21 changes: 21 additions & 0 deletions lib/irb/cmd/backtrace.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions lib/irb/cmd/break.rb
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions lib/irb/cmd/catch.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions lib/irb/cmd/continue.rb
Original file line number Diff line number Diff line change
@@ -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
12 changes: 10 additions & 2 deletions lib/irb/cmd/debug.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(pre_cmds: nil, do_cmds: nil)
unless binding_irb?
puts "`debug` command is only available when IRB is started with binding.irb"
return
Expand All @@ -25,11 +25,19 @@ def execute(*args)
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
Expand Down
17 changes: 17 additions & 0 deletions lib/irb/cmd/delete.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions lib/irb/cmd/finish.rb
Original file line number Diff line number Diff line change
@@ -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
31 changes: 9 additions & 22 deletions lib/irb/cmd/info.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
34 changes: 34 additions & 0 deletions lib/irb/cmd/irb_info.rb
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions lib/irb/cmd/next.rb
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions lib/irb/cmd/step.rb
Original file line number Diff line number Diff line change
@@ -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
16 changes: 11 additions & 5 deletions lib/irb/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
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
37 changes: 36 additions & 1 deletion lib/irb/extend-command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,48 @@ def irb_context
:irb_edit, :Edit, "cmd/edit",
[: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"
],

[
Expand Down
5 changes: 5 additions & 0 deletions lib/irb/init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,13 @@ def IRB.init_config(ap_path)
@CONF[:AT_EXIT] = []

@CONF[:COMMAND_ALIASES] = {
# Symbol aliases
:'$' => :show_source,
:'@' => :whereami,
# Keyword aliases
:break => :irb_break,
:catch => :irb_catch,
:next => :irb_next,
}
end

Expand Down
Loading