diff --git a/README.md b/README.md index a3985eac2..eadbae50c 100644 --- a/README.md +++ b/README.md @@ -441,6 +441,10 @@ The `<...>` notation means the argument. * Show predecessor lines as opposed to the `list` command. * `l[ist] ` or `l[ist] -` * Show current frame's source code from the line to if given. +* `ls` + * Executes `irb`'s `ls` command with current binding. +* `ls ` + * Executes `irb`'s `ls` command with the given obj. * `edit` * Open the current file on the editor (use `EDITOR` environment variable). * Note that edited file will not be reloaded. diff --git a/lib/debug/session.rb b/lib/debug/session.rb index cb9f85255..0a4d10c43 100644 --- a/lib/debug/session.rb +++ b/lib/debug/session.rb @@ -481,6 +481,17 @@ def process_command line return :retry end + # * `ls` + # * Executes `irb`'s `ls` command with current binding. + # * `ls ` + # * Executes `irb`'s `ls` command with the given obj. + when 'ls' + if @ui.remote? + @ui.puts "not supported on the remote console." + return :retry + end + @tc << [:irb, :ls, arg] + # * `edit` # * Open the current file on the editor (use `EDITOR` environment variable). # * Note that edited file will not be reloaded. diff --git a/lib/debug/thread_client.rb b/lib/debug/thread_client.rb index 916a11f6e..e38fe9bee 100644 --- a/lib/debug/thread_client.rb +++ b/lib/debug/thread_client.rb @@ -2,6 +2,7 @@ require 'objspace' require 'pp' +require "irb" require_relative 'frame_info' require_relative 'color' @@ -15,6 +16,7 @@ def self.current end end + extend IRB::ExtendCommandBundle include Color attr_reader :location, :thread, :mode, :id @@ -579,6 +581,26 @@ def wait_next_action else raise "unsupported frame operation: #{arg.inspect}" end + event! :result, nil + when :irb + cmd, arg_expr = *args + + raise "unsupported irb command: #{cmd}" unless cmd == :ls + + begin + # most irb commands require irb workplace or irb session present in the environment to work + init_irb_context + + if arg_expr + arg = frame_eval(arg_expr) + self.class.send("irb_#{cmd}", arg) + else + self.class.send("irb_#{cmd}") + end + ensure + reset_irb_context + end + event! :result, nil when :show type = args.shift @@ -658,5 +680,16 @@ def to_s str += " (not under control)" unless self.mode str end + + def init_irb_context + IRB.init_config(nil) + workspace = IRB::WorkSpace.new(current_frame.binding) + irb = IRB::Irb.new(workspace, nil) + IRB.conf[:MAIN_CONTEXT] = irb.context + end + + def reset_irb_context + IRB.conf[:MAIN_CONTEXT] = nil + end end end diff --git a/test/debug/ls_test.rb b/test/debug/ls_test.rb new file mode 100644 index 000000000..e121cf71a --- /dev/null +++ b/test/debug/ls_test.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require_relative '../support/test_case' + +module DEBUGGER__ + class LsTest < TestCase + def program + <<~RUBY + 1| class Foo + 2| def initialize + 3| @var = "foobar" + 4| end + 5| + 6| def bar; end + 7| def self.baz; end + 8| end + 9| + 10| foo = Foo.new + 11| + 12| binding.b + RUBY + end + + def test_ls_lists_local_variables + debug_code(program, remote: false) do + type 'c' + type 'ls' + assert_line_text(/locals.*: _ foo/) + type 'c' + end + end + + def test_ls_lists_object_info + debug_code(program, remote: false) do + type 'c' + type 'ls foo' + assert_line_text([ + /Foo#methods.*: bar/, + /instance variables.*: @var/ + ]) + type 'c' + end + end + + def test_ls_lists_class_info + debug_code(program, remote: false) do + type 'c' + type 'ls Foo' + assert_line_text( + [ + /Class#methods.*: allocate/, + /Foo\.methods.*: baz/, + ] + ) + type 'c' + end + end + end +end