summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Lo <stan001212@gmail.com>2023-07-25 19:33:18 +0100
committergit <svn-admin@ruby-lang.org>2023-07-25 18:33:23 +0000
commitd8cee5507361b2cd3f3baccb8fda45bad8389a70 (patch)
tree90550675d7f90c2fab62701d6135e3355e9e5987
parente1104017e3080fd432c0b5fdc3ae6e004ffd0834 (diff)
[ruby/irb] Display `show_cmds`'s output in a pager when in TTY
environment (https://github.com/ruby/irb/pull/647) This can: - Make it easier to scroll up and down the commands list - Avoid pushing up users' previous output - Allow users to do basic search with `/<word>` https://github.com/ruby/irb/commit/f94e8a66dd
-rw-r--r--lib/irb/cmd/show_cmds.rb7
-rw-r--r--lib/irb/pager.rb61
-rw-r--r--test/irb/test_cmd.rb10
-rw-r--r--test/irb/yamatanooroti/test_rendering.rb18
4 files changed, 93 insertions, 3 deletions
diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/cmd/show_cmds.rb
index 490561825e..1c9f1ea19a 100644
--- a/lib/irb/cmd/show_cmds.rb
+++ b/lib/irb/cmd/show_cmds.rb
@@ -2,6 +2,7 @@
require "stringio"
require_relative "nop"
+require_relative "../pager"
module IRB
# :stopdoc:
@@ -28,9 +29,9 @@ module IRB
output.puts
end
- puts output.string
-
- nil
+ Pager.page do |io|
+ io.puts output.string
+ end
end
end
end
diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb
new file mode 100644
index 0000000000..16066cf273
--- /dev/null
+++ b/lib/irb/pager.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+module IRB
+ # The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb.
+ # Please do NOT use this class directly outside of IRB.
+ class Pager
+ PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq
+
+ class << self
+ def page
+ if STDIN.tty? && pager = setup_pager
+ begin
+ pid = pager.pid
+ yield pager
+ ensure
+ pager.close
+ end
+ else
+ yield $stdout
+ end
+ # When user presses Ctrl-C, IRB would raise `IRB::Abort`
+ # But since Pager is implemented by running paging commands like `less` in another process with `IO.popen`,
+ # the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager
+ # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process
+ rescue IRB::Abort
+ Process.kill("TERM", pid) if pid
+ nil
+ rescue Errno::EPIPE
+ end
+
+ private
+
+ def setup_pager
+ require 'shellwords'
+
+ PAGE_COMMANDS.each do |pager|
+ pager = Shellwords.split(pager)
+ next if pager.empty?
+
+ if pager.first == 'less' || pager.first == 'more'
+ pager << '-R' unless pager.include?('-R')
+ end
+
+ begin
+ io = IO.popen(pager, 'w')
+ rescue
+ next
+ end
+
+ if $? && $?.pid == io.pid && $?.exited? # pager didn't work
+ next
+ end
+
+ return io
+ end
+
+ nil
+ end
+ end
+ end
+end
diff --git a/test/irb/test_cmd.rb b/test/irb/test_cmd.rb
index 71df60db14..71d64a0ec2 100644
--- a/test/irb/test_cmd.rb
+++ b/test/irb/test_cmd.rb
@@ -688,6 +688,16 @@ module TestIRB
class ShowCmdsTest < CommandTestCase
+ def setup
+ STDIN.singleton_class.define_method :tty? do
+ false
+ end
+ end
+
+ def teardown
+ STDIN.singleton_class.remove_method :tty?
+ end
+
def test_show_cmds
out, err = execute_lines(
"show_cmds\n"
diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb
index d2342d6a23..12467b0913 100644
--- a/test/irb/yamatanooroti/test_rendering.rb
+++ b/test/irb/yamatanooroti/test_rendering.rb
@@ -252,6 +252,24 @@ class IRB::RenderingTest < Yamatanooroti::TestCase
EOC
end
+ def test_show_cmds_with_pager_can_quit_with_ctrl_c
+ write_irbrc <<~'LINES'
+ puts 'start IRB'
+ LINES
+ start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB')
+ write("show_cmds\n")
+ write("G") # move to the end of the screen
+ write("\C-c") # quit pager
+ write("'foo' + 'bar'\n") # eval something to make sure IRB resumes
+ close
+
+ screen = result.join("\n").sub(/\n*\z/, "\n")
+ # IRB::Abort should be rescued
+ assert_not_match(/IRB::Abort/, screen)
+ # IRB should resume
+ assert_match(/foobar/, screen)
+ end
+
private
def write_irbrc(content)