From 0e9db419beba215e97ec9f221421c89d3d2ce9ab Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Fri, 16 Feb 2024 16:12:50 +0000 Subject: [PATCH] Support repeating debugger input by passing empty input to it (#856) * Test IRB's behaviour with empty input * Handle empty input and pass it to debugger Since `rdbg` accepts empty input to repeat the previous command, IRB should take empty input in `irb:rdbg` sessions and pass them to the debugger. Currently, IRB simply ignores empty input and does nothing. This commit creates `EmptyInput` to represent empty input so it can fit into the current IRB's input processing flow in `Irb#eval_input`. --- lib/irb.rb | 9 ++++--- lib/irb/statement.rb | 23 ++++++++++++++++ ...ug_cmd.rb => test_debugger_integration.rb} | 26 ++++++++++++++++++- test/irb/test_irb.rb | 18 +++++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) rename test/irb/{test_debug_cmd.rb => test_debugger_integration.rb} (93%) diff --git a/lib/irb.rb b/lib/irb.rb index 67e03f8bc..fd06626e9 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1082,16 +1082,17 @@ def each_top_level_statement loop do code = readmultiline break unless code - - if code != "\n" - yield build_statement(code), @line_no - end + yield build_statement(code), @line_no @line_no += code.count("\n") rescue RubyLex::TerminateLineInput end end def build_statement(code) + if code.match?(/\A\n*\z/) + return Statement::EmptyInput.new + end + code.force_encoding(@context.io.encoding) command_or_alias, arg = code.split(/\s/, 2) # Transform a non-identifier alias (@, $) or keywords (next, break) diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index b12110600..4e17e5143 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -20,6 +20,29 @@ def evaluable_code raise NotImplementedError end + class EmptyInput < Statement + def is_assignment? + false + end + + def suppresses_echo? + true + end + + # Debugger takes empty input to repeat the last command + def should_be_handled_by_debugger? + true + end + + def code + "" + end + + def evaluable_code + code + end + end + class Expression < Statement def initialize(code, is_assignment) @code = code diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debugger_integration.rb similarity index 93% rename from test/irb/test_debug_cmd.rb rename to test/irb/test_debugger_integration.rb index 0fb45af47..d95b01c3d 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debugger_integration.rb @@ -6,7 +6,7 @@ require_relative "helper" module TestIRB - class DebugCommandTest < IntegrationTestCase + class DebuggerIntegrationTest < IntegrationTestCase def setup super @@ -434,5 +434,29 @@ def test_multi_irb_commands_are_not_available_after_activating_the_debugger assert_match(/irb\(main\):001> next/, output) assert_include(output, "Multi-IRB commands are not available when the debugger is enabled.") end + + def test_irb_passes_empty_input_to_debugger_to_repeat_the_last_command + write_ruby <<~'ruby' + binding.irb + puts "foo" + puts "bar" + puts "baz" + ruby + + output = run_ruby_file do + type "next" + type "" + # Test that empty input doesn't repeat expressions + type "123" + type "" + type "next" + type "" + type "" + end + + assert_include(output, "=> 2\| puts \"foo\"") + assert_include(output, "=> 3\| puts \"bar\"") + assert_include(output, "=> 4\| puts \"baz\"") + end end end diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index fb8b5c2bf..8c4fb5dde 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -58,6 +58,24 @@ def test_symbol_aliases_dont_affect_ruby_syntax assert_include output, "=> \"It's a foo\"" assert_include output, "=> \"It's a bar\"" end + + def test_empty_input_echoing_behaviour + write_ruby <<~'RUBY' + binding.irb + RUBY + + output = run_ruby_file do + type "" + type " " + type "exit" + end + + # Input cramped together due to how Reline's Reline::GeneralIO works + assert_include( + output, + "irb(main):001> irb(main):002> irb(main):002> irb(main):002> => nil\r\n" + ) + end end class IrbIOConfigurationTest < TestCase