diff --git a/lib/irb.rb b/lib/irb.rb index 67e03f8bc92cf1..fd06626e9b6ae5 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 b12110600cc728..4e17e51434a5c4 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 0fb45af478e8cb..d95b01c3ddecf2 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 fb8b5c2bfa37f8..8c4fb5ddee3d7a 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