pry--pry/lib/pry/pager.rb

207 lines
4.7 KiB
Ruby
Raw Normal View History

require 'pry/terminal'
# A Pry::Pager is an IO-like object that accepts text and either prints it
# immediately, prints it one page at a time, or streams it to an external
# program to print one page at a time.
2012-08-08 21:57:19 +00:00
class Pry::Pager
class StopPaging < StandardError
end
2012-08-09 21:51:50 +00:00
# @param [String] text
# A piece of text to run through a pager.
# @param [Symbol?] pager_type
2012-11-04 03:59:21 +00:00
# `:simple` -- Use the pure ruby pager.
# `:system` -- Use the system pager (less) or the environment variable
# $PAGER if set.
# `nil` -- Infer what pager to use from the environment. What this
# really means is that JRuby and systems that do not have
# access to 'less' will run through the pure ruby pager.
def self.page(text, pager_type = nil)
pager = best_available($stdout, pager_type)
pager << text
ensure
pager.close if pager
end
def self.best_available(output, pager_type = nil)
case pager_type
2012-08-09 15:43:46 +00:00
when nil
no_pager = !SystemPager.available?
if no_pager || Pry::Helpers::BaseHelpers.jruby?
SimplePager.new(output)
else
SystemPager.new(output)
end
2012-08-09 15:43:46 +00:00
when :simple
SimplePager.new(output)
2012-08-09 15:43:46 +00:00
when :system
SystemPager.new(output)
2012-08-09 15:43:46 +00:00
else
2012-12-02 16:45:42 +00:00
raise "'#{pager}' is not a recognized pager."
2012-08-09 15:43:46 +00:00
end
2012-08-08 21:57:19 +00:00
end
def initialize(out)
@out = out
end
def puts(str)
print "#{str.chomp}\n"
end
def print(str)
write str
end
alias << print
2013-11-02 21:14:51 +00:00
def write(str)
@out.write str
end
def close
# no-op for base pager, but important for subclasses
end
2013-11-02 21:14:51 +00:00
private
def height
@height ||= Pry::Terminal.height!
end
def width
@width ||= Pry::Terminal.width!
end
2013-11-02 21:14:51 +00:00
class SimplePager < Pry::Pager
def initialize(*)
super
2013-11-02 21:14:51 +00:00
@tracker = PageTracker.new(height - 3, width)
end
def write(str)
str.lines.each do |line|
@out.write line
2013-11-02 21:14:51 +00:00
@tracker.record line
2013-11-02 21:14:51 +00:00
if @tracker.page?
@out.puts "\n<page break> --- Press enter to continue " \
"( q<enter> to break ) --- <page break>"
raise StopPaging if $stdin.gets.chomp == "q"
2013-11-02 21:14:51 +00:00
@tracker.reset
end
end
end
end
2013-11-02 21:14:51 +00:00
# SystemPager buffers output until we're pretty sure it's at least a
# page long, then invokes an external pager and starts streaming
# output to it. If close is called before then, it just prints out
# the buffered content.
class SystemPager < Pry::Pager
def self.default_pager
pager = ENV["PAGER"] || ""
# Default to less, and make sure less is being passed the correct options
2013-11-02 21:14:51 +00:00
if pager.strip.empty? or pager =~ /^less\b/
pager = "less -R -S -F -X"
end
pager
end
def self.available?
if @system_pager.nil?
@system_pager = begin
pager_executable = default_pager.split(' ').first
`which #{ pager_executable }`
rescue
false
end
else
@system_pager
end
end
def initialize(*)
super
2013-11-02 21:14:51 +00:00
@tracker = PageTracker.new(height, width)
@buffer = ""
end
def write(str)
2013-11-02 21:14:51 +00:00
if invoked_pager?
pager.write str
else
@tracker.record str
@buffer << str
if @tracker.page?
pager.write @buffer
end
end
rescue Errno::EPIPE
end
def close
2013-11-02 21:14:51 +00:00
if invoked_pager?
pager.close
else
@out.puts @buffer
end
end
private
def invoked_pager?
@pager
end
def pager
@pager ||= IO.popen(self.class.default_pager, 'w')
end
end
# PageTracker tracks output to determine whether it's likely to take up a
# whole page. This doesn't need to be super precise, but we can use it for
# SimplePager and to avoid invoking the system pager unnecessarily.
#
# One simplifying assumption is that we don't need page? to return true on
# the basis of an incomplete line. Long lines should be counted as multiple
# lines, but we don't have to transition from false to true until we see a
# newline.
class PageTracker
def initialize(rows, cols)
@rows, @cols = rows, cols
reset
end
def record(str)
str.lines.each do |line|
if line.end_with? "\n"
@row += ((@col + line_length(line) - 1) / @cols) + 1
@col = 0
else
@col += line_length(line)
2013-11-02 21:14:51 +00:00
end
end
end
def page?
@row >= @rows
end
def reset
@row = 0
@col = 0
end
private
# Approximation of the printable length of a given line, without the
# newline and without ANSI color codes.
def line_length(line)
line.chomp.gsub(/\e\[[\d;]*m/, '').length
2012-08-08 21:57:19 +00:00
end
end
end