From 4fedce93d3cd6c3283f938982936d4935d8d64bd Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 30 Nov 2023 15:22:17 +0000 Subject: [PATCH] Page evaluation result's output (#784) * Page evaluation result's output This will make it easier to work with long output that exceeds the terminal's height. * Use consistent TERM in rendering tests This makes sure we get consistent result on all platforms. --- lib/irb.rb | 8 +++-- lib/irb/pager.rb | 23 ++++++++------- test/irb/helper.rb | 14 +++++++-- test/irb/test_context.rb | 1 + test/irb/test_debug_cmd.rb | 4 --- test/irb/test_irb.rb | 5 ---- test/irb/yamatanooroti/test_rendering.rb | 37 ++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 25 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 66149eb45..1ba335c08 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -20,6 +20,7 @@ require_relative "irb/version" require_relative "irb/easter-egg" require_relative "irb/debug" +require_relative "irb/pager" # IRB stands for "interactive Ruby" and is a tool to interactively execute Ruby # expressions read from the standard input. @@ -859,11 +860,12 @@ def output_value(omit = false) # :nodoc: end end end + if multiline_p && @context.newline_before_multiline_output? - printf @context.return_format, "\n#{str}" - else - printf @context.return_format, str + str = "\n" + str end + + Pager.page_content(format(@context.return_format, str), retain_content: true) end # Outputs the local variables to this current session, including diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index e38d97e3c..a0db5e93b 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -7,9 +7,9 @@ class Pager PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq class << self - def page_content(content) + def page_content(content, **options) if content_exceeds_screen_height?(content) - page do |io| + page(**options) do |io| io.puts content end else @@ -17,8 +17,8 @@ def page_content(content) end end - def page - if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager + def page(retain_content: false) + if IRB.conf[:USE_PAGER] && STDIN.tty? && pager = setup_pager(retain_content: retain_content) begin pid = pager.pid yield pager @@ -55,19 +55,20 @@ def content_exceeds_screen_height?(content) pageable_height * screen_width < Reline::Unicode.calculate_width(content, true) end - def setup_pager + def setup_pager(retain_content:) require 'shellwords' - PAGE_COMMANDS.each do |pager| - pager = Shellwords.split(pager) - next if pager.empty? + PAGE_COMMANDS.each do |pager_cmd| + cmd = Shellwords.split(pager_cmd) + next if cmd.empty? - if pager.first == 'less' - pager << '-R' unless pager.include?('-R') + if cmd.first == 'less' + cmd << '-R' unless cmd.include?('-R') + cmd << '-X' if retain_content && !cmd.include?('-X') end begin - io = IO.popen(pager, 'w') + io = IO.popen(cmd, 'w') rescue next end diff --git a/test/irb/helper.rb b/test/irb/helper.rb index ede48be64..38bdbb4c3 100644 --- a/test/irb/helper.rb +++ b/test/irb/helper.rb @@ -93,6 +93,10 @@ def setup if ruby_core? omit "This test works only under ruby/irb" end + + write_rc <<~RUBY + IRB.conf[:USE_PAGER] = false + RUBY end def teardown @@ -197,8 +201,14 @@ def write_ruby(program) end def write_rc(content) - @irbrc = Tempfile.new('irbrc') - @tmpfiles << @irbrc + # Append irbrc content if a tempfile for it already exists + if @irbrc + @irbrc = File.open(@irbrc, "a") + else + @irbrc = Tempfile.new('irbrc') + @tmpfiles << @irbrc + end + @irbrc.write(content) @irbrc.close @envs['IRBRC'] = @irbrc.path diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index f4a19ee3c..79186f13a 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -11,6 +11,7 @@ def setup IRB.init_config(nil) IRB.conf[:USE_SINGLELINE] = false IRB.conf[:VERBOSE] = false + IRB.conf[:USE_PAGER] = false workspace = IRB::WorkSpace.new(Object.new) @context = IRB::Context.new(nil, workspace, TestInputMethod.new) diff --git a/test/irb/test_debug_cmd.rb b/test/irb/test_debug_cmd.rb index 53d40f729..0fb45af47 100644 --- a/test/irb/test_debug_cmd.rb +++ b/test/irb/test_debug_cmd.rb @@ -346,10 +346,6 @@ def test_help_command_is_delegated_to_the_debugger end def test_show_cmds_display_different_content_when_debugger_is_enabled - write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false - RUBY - write_ruby <<~'ruby' binding.irb ruby diff --git a/test/irb/test_irb.rb b/test/irb/test_irb.rb index e6eb3d5da..fb8b5c2bf 100644 --- a/test/irb/test_irb.rb +++ b/test/irb/test_irb.rb @@ -6,10 +6,6 @@ module TestIRB class InputTest < IntegrationTestCase def test_symbol_aliases_are_handled_correctly - write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false - RUBY - write_ruby <<~'RUBY' class Foo end @@ -26,7 +22,6 @@ class Foo def test_symbol_aliases_are_handled_correctly_with_singleline_mode write_rc <<~RUBY - IRB.conf[:USE_PAGER] = false IRB.conf[:USE_SINGLELINE] = true RUBY diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index ca8176dfe..b18b95bbb 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -10,6 +10,8 @@ class IRB::RenderingTest < Yamatanooroti::TestCase def setup + @original_term = ENV['TERM'] + ENV['TERM'] = "xterm-256color" @pwd = Dir.pwd suffix = '%010d' % Random.rand(0..65535) @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_irb_#{$$}_#{suffix}") @@ -27,6 +29,7 @@ def setup def teardown FileUtils.rm_rf(@tmpdir) ENV['IRBRC'] = @irbrc_backup + ENV['TERM'] = @original_term ENV.delete('RELINE_TEST_PROMPT') if ENV['RELINE_TEST_PROMPT'] end @@ -377,6 +380,40 @@ def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen assert_match(/foobar/, screen) end + def test_long_evaluation_output_is_paged + write_irbrc <<~'LINES' + puts 'start IRB' + require "irb/pager" + LINES + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write("'a' * 80 * 11\n") + write("'foo' + 'bar'\n") # eval something to make sure IRB resumes + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + assert_match(/(a{80}\n){8}/, screen) + # because pager is invoked, foobar will not be evaluated + assert_not_match(/foobar/, screen) + end + + def test_long_evaluation_output_is_preserved_after_paging + write_irbrc <<~'LINES' + puts 'start IRB' + require "irb/pager" + LINES + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + write("'a' * 80 * 11\n") + write("q") # quit pager + write("'foo' + 'bar'\n") # eval something to make sure IRB resumes + close + + screen = result.join("\n").sub(/\n*\z/, "\n") + # confirm pager has exited + assert_match(/foobar/, screen) + # confirm output is preserved + assert_match(/(a{80}\n){6}/, screen) + end + def test_debug_integration_hints_debugger_commands write_irbrc <<~'LINES' IRB.conf[:USE_COLORIZE] = false