Skip to content

Commit

Permalink
Page evaluation result's output (#784)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
st0012 authored Nov 30, 2023
1 parent a4868a5 commit 4fedce9
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 25 deletions.
8 changes: 5 additions & 3 deletions lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
23 changes: 12 additions & 11 deletions lib/irb/pager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ 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
$stdout.puts 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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions test/irb/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/irb/test_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 0 additions & 4 deletions test/irb/test_debug_cmd.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions test/irb/test_irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
37 changes: 37 additions & 0 deletions test/irb/yamatanooroti/test_rendering.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 4fedce9

Please sign in to comment.