From e9609bf4d51504fb0e0af3f95f0c73205226748d Mon Sep 17 00:00:00 2001 From: st0012 Date: Wed, 14 Jul 2021 10:40:44 +0800 Subject: [PATCH 1/4] Add ls test cases --- test/debug/ls_test.rb | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/debug/ls_test.rb diff --git a/test/debug/ls_test.rb b/test/debug/ls_test.rb new file mode 100644 index 000000000..bfda8429b --- /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 From 83a3c6242c847a76fc06ee8f4db2c95139569f2b Mon Sep 17 00:00:00 2001 From: st0012 Date: Wed, 14 Jul 2021 10:44:20 +0800 Subject: [PATCH 2/4] Introduce ls command --- README.md | 5 ++ lib/debug/command/ls.rb | 96 ++++++++++++++++++++++++++++++++++++++ lib/debug/session.rb | 8 ++++ lib/debug/thread_client.rb | 12 +++++ 4 files changed, 121 insertions(+) create mode 100644 lib/debug/command/ls.rb diff --git a/README.md b/README.md index 68a1cedea..62e016e06 100644 --- a/README.md +++ b/README.md @@ -487,6 +487,11 @@ The `<...>` notation means the argument. * Filter the output with ``. * `i[nfo] th[read[s]]` * Show all threads (same as `th[read]`). +* `ls` + * Show you available methods, constants, local variables, and instance variables in the current scope. +* `ls ` + * Show you available methods, local variables, and instance variables of the given object. + * If the object is a class/module, it also lists its constants. * `display` * Show display setting. * `display ` diff --git a/lib/debug/command/ls.rb b/lib/debug/command/ls.rb new file mode 100644 index 000000000..8ae12e9a2 --- /dev/null +++ b/lib/debug/command/ls.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module DEBUGGER__ + module Command + class Ls + class << self + def execute(current_frame, obj) + o = Output.new + + locals = current_frame.binding.local_variables + klass = (obj.class == Class || obj.class == Module ? obj : obj.class) + + o.dump("constants", obj.constants) if obj.respond_to?(:constants) + dump_methods(o, klass, obj) + o.dump("instance variables", obj.instance_variables) + o.dump("class variables", klass.class_variables) + o.dump("locals", locals) + end + + def dump_methods(o, klass, obj) + singleton_class = begin obj.singleton_class; rescue TypeError; nil end + maps = class_method_map((singleton_class || klass).ancestors) + maps.each do |mod, methods| + name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" + o.dump(name, methods) + end + end + + def class_method_map(classes) + dumped = Array.new + classes.reject { |mod| mod >= Object }.map do |mod| + methods = mod.public_instance_methods(false).select do |m| + dumped.push(m) unless dumped.include?(m) + end + [mod, methods] + end.reverse + end + end + + class Output + include Color + + MARGIN = " " + + def initialize + @line_width = screen_width - MARGIN.length # right padding + end + + def dump(name, strs) + strs = strs.sort + return if strs.empty? + + # Attempt a single line + print "#{colorize_blue(name)}: " + if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) + puts strs.join(MARGIN) + return + end + puts + + # Dump with the largest # of columns that fits on a line + cols = strs.size + until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 + cols -= 1 + end + widths = col_widths(strs, cols: cols) + strs.each_slice(cols) do |ss| + puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join + end + end + + private + + def fits_on_line?(strs, cols:, offset: 0) + width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) + width <= @line_width - offset + end + + def col_widths(strs, cols:) + cols.times.map do |col| + (col...strs.size).step(cols).map do |i| + strs[i].length + end.max + end + end + + def screen_width + SESSION.width + rescue Errno::EINVAL # in `winsize': Invalid argument - + 80 + end + end + private_constant :Output + end + end +end diff --git a/lib/debug/session.rb b/lib/debug/session.rb index 765ce65fe..9bbcc4b8c 100644 --- a/lib/debug/session.rb +++ b/lib/debug/session.rb @@ -545,6 +545,14 @@ def process_command line return :retry end + # * `ls` + # * Show you available methods, constants, local variables, and instance variables in the current scope. + # * `ls ` + # * Show you available methods, local variables, and instance variables of the given object. + # * If the object is a class/module, it also lists its constants. + when 'ls' + @tc << [:ls, arg] + # * `display` # * Show display setting. # * `display ` diff --git a/lib/debug/thread_client.rb b/lib/debug/thread_client.rb index a56d22898..3cf333817 100644 --- a/lib/debug/thread_client.rb +++ b/lib/debug/thread_client.rb @@ -5,6 +5,7 @@ require_relative 'frame_info' require_relative 'color' +require_relative 'command/ls' module DEBUGGER__ class ThreadClient @@ -632,6 +633,17 @@ def wait_next_action else raise "unsupported frame operation: #{arg.inspect}" end + event! :result, nil + when :ls + subject = + if arg_expr = args.first + frame_eval(arg_expr) + else + frame_eval("self") + end + + Command::Ls.execute(current_frame, subject) + event! :result, nil when :show type = args.shift From e3d0f82626f903092735733b5c1a3ff14aaf0bc9 Mon Sep 17 00:00:00 2001 From: st0012 Date: Wed, 14 Jul 2021 11:01:04 +0800 Subject: [PATCH 3/4] Use TC output instead of stdout --- lib/debug/command/ls.rb | 19 ++++++++++++------- lib/debug/thread_client.rb | 2 +- test/debug/ls_test.rb | 6 +++--- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/debug/command/ls.rb b/lib/debug/command/ls.rb index 8ae12e9a2..1d217c3e6 100644 --- a/lib/debug/command/ls.rb +++ b/lib/debug/command/ls.rb @@ -4,8 +4,8 @@ module DEBUGGER__ module Command class Ls class << self - def execute(current_frame, obj) - o = Output.new + def execute(current_frame, obj, output) + o = Output.new(output) locals = current_frame.binding.local_variables klass = (obj.class == Class || obj.class == Module ? obj : obj.class) @@ -42,7 +42,8 @@ class Output MARGIN = " " - def initialize + def initialize(output) + @output = output @line_width = screen_width - MARGIN.length # right padding end @@ -50,13 +51,17 @@ def dump(name, strs) strs = strs.sort return if strs.empty? + line = "#{colorize_blue(name)}: " + # Attempt a single line - print "#{colorize_blue(name)}: " if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) - puts strs.join(MARGIN) + line += strs.join(MARGIN) + @output << line return end - puts + + # Multi-line + @output << line # Dump with the largest # of columns that fits on a line cols = strs.size @@ -65,7 +70,7 @@ def dump(name, strs) end widths = col_widths(strs, cols: cols) strs.each_slice(cols) do |ss| - puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join + @output << ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join end end diff --git a/lib/debug/thread_client.rb b/lib/debug/thread_client.rb index 3cf333817..2597b497d 100644 --- a/lib/debug/thread_client.rb +++ b/lib/debug/thread_client.rb @@ -642,7 +642,7 @@ def wait_next_action frame_eval("self") end - Command::Ls.execute(current_frame, subject) + Command::Ls.execute(current_frame, subject, @output) event! :result, nil when :show diff --git a/test/debug/ls_test.rb b/test/debug/ls_test.rb index bfda8429b..9fdbce3b5 100644 --- a/test/debug/ls_test.rb +++ b/test/debug/ls_test.rb @@ -22,7 +22,7 @@ def program end def test_ls_lists_local_variables - debug_code(program, remote: false) do + debug_code(program) do type 'c' type 'ls' assert_line_text(/locals: foo/) @@ -31,7 +31,7 @@ def test_ls_lists_local_variables end def test_ls_lists_object_info - debug_code(program, remote: false) do + debug_code(program) do type 'c' type 'ls foo' assert_line_text([ @@ -43,7 +43,7 @@ def test_ls_lists_object_info end def test_ls_lists_class_info - debug_code(program, remote: false) do + debug_code(program) do type 'c' type 'ls Foo' assert_line_text( From 626b98d34c2a8eb14fe20b30d903e1c65c8e964d Mon Sep 17 00:00:00 2001 From: st0012 Date: Sat, 17 Jul 2021 10:30:53 +0800 Subject: [PATCH 4/4] Rename ls to o[utline] --- README.md | 6 +++--- lib/debug/command/{ls.rb => outline.rb} | 2 +- lib/debug/session.rb | 10 ++++----- lib/debug/thread_client.rb | 6 +++--- test/debug/{ls_test.rb => outline_test.rb} | 25 ++++++++++++++++------ 5 files changed, 30 insertions(+), 19 deletions(-) rename lib/debug/command/{ls.rb => outline.rb} (99%) rename test/debug/{ls_test.rb => outline_test.rb} (67%) diff --git a/README.md b/README.md index 62e016e06..9ca725d9c 100644 --- a/README.md +++ b/README.md @@ -487,10 +487,10 @@ The `<...>` notation means the argument. * Filter the output with ``. * `i[nfo] th[read[s]]` * Show all threads (same as `th[read]`). -* `ls` +* `o[utline]` or `ls` * Show you available methods, constants, local variables, and instance variables in the current scope. -* `ls ` - * Show you available methods, local variables, and instance variables of the given object. +* `o[utline] ` or `ls ` + * Show you available methods and instance variables of the given object. * If the object is a class/module, it also lists its constants. * `display` * Show display setting. diff --git a/lib/debug/command/ls.rb b/lib/debug/command/outline.rb similarity index 99% rename from lib/debug/command/ls.rb rename to lib/debug/command/outline.rb index 1d217c3e6..32a8194ab 100644 --- a/lib/debug/command/ls.rb +++ b/lib/debug/command/outline.rb @@ -2,7 +2,7 @@ module DEBUGGER__ module Command - class Ls + class Outline class << self def execute(current_frame, obj, output) o = Output.new(output) diff --git a/lib/debug/session.rb b/lib/debug/session.rb index 9bbcc4b8c..79c1a6c47 100644 --- a/lib/debug/session.rb +++ b/lib/debug/session.rb @@ -545,13 +545,13 @@ def process_command line return :retry end - # * `ls` + # * `o[utline]` or `ls` # * Show you available methods, constants, local variables, and instance variables in the current scope. - # * `ls ` - # * Show you available methods, local variables, and instance variables of the given object. + # * `o[utline] ` or `ls ` + # * Show you available methods and instance variables of the given object. # * If the object is a class/module, it also lists its constants. - when 'ls' - @tc << [:ls, arg] + when 'outline', 'o', 'ls' + @tc << [:outline, arg] # * `display` # * Show display setting. diff --git a/lib/debug/thread_client.rb b/lib/debug/thread_client.rb index 2597b497d..6f7dd871b 100644 --- a/lib/debug/thread_client.rb +++ b/lib/debug/thread_client.rb @@ -5,7 +5,7 @@ require_relative 'frame_info' require_relative 'color' -require_relative 'command/ls' +require_relative 'command/outline' module DEBUGGER__ class ThreadClient @@ -634,7 +634,7 @@ def wait_next_action raise "unsupported frame operation: #{arg.inspect}" end event! :result, nil - when :ls + when :outline subject = if arg_expr = args.first frame_eval(arg_expr) @@ -642,7 +642,7 @@ def wait_next_action frame_eval("self") end - Command::Ls.execute(current_frame, subject, @output) + Command::Outline.execute(current_frame, subject, @output) event! :result, nil when :show diff --git a/test/debug/ls_test.rb b/test/debug/outline_test.rb similarity index 67% rename from test/debug/ls_test.rb rename to test/debug/outline_test.rb index 9fdbce3b5..dcc1d6246 100644 --- a/test/debug/ls_test.rb +++ b/test/debug/outline_test.rb @@ -3,7 +3,7 @@ require_relative '../support/test_case' module DEBUGGER__ - class LsTest < TestCase + class OutlineTest < TestCase def program <<~RUBY 1| class Foo @@ -21,19 +21,19 @@ def program RUBY end - def test_ls_lists_local_variables + def test_outline_lists_local_variables debug_code(program) do type 'c' - type 'ls' + type 'outline' assert_line_text(/locals: foo/) type 'c' end end - def test_ls_lists_object_info + def test_outline_lists_object_info debug_code(program) do type 'c' - type 'ls foo' + type 'outline foo' assert_line_text([ /Foo#methods: bar/, /instance variables: @var/ @@ -42,10 +42,10 @@ def test_ls_lists_object_info end end - def test_ls_lists_class_info + def test_outline_lists_class_info debug_code(program) do type 'c' - type 'ls Foo' + type 'outline Foo' assert_line_text( [ /Class#methods: allocate/, @@ -55,5 +55,16 @@ def test_ls_lists_class_info type 'c' end end + + def test_outline_alisases + debug_code(program) do + type 'c' + type 'outline' + assert_line_text(/locals: foo/) + type 'ls' + assert_line_text(/locals: foo/) + type 'c' + end + end end end