2013-11-03 19:35:33 -05:00
|
|
|
# A pager is an `IO`-like object that accepts text and either prints it
|
2013-10-29 00:25:13 -04:00
|
|
|
# immediately, prints it one page at a time, or streams it to an external
|
|
|
|
# program to print one page at a time.
|
2018-11-07 05:49:14 -05:00
|
|
|
class Pry
|
|
|
|
class Pager
|
|
|
|
class StopPaging < StandardError
|
2012-08-09 11:43:46 -04:00
|
|
|
end
|
2012-08-08 17:57:19 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
attr_reader :_pry_
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def initialize(_pry_)
|
|
|
|
@_pry_ = _pry_
|
2012-08-09 11:43:46 -04:00
|
|
|
end
|
2012-08-09 17:51:50 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# Send the given text through the best available pager (if
|
|
|
|
# `Pry.config.pager` is enabled). If you want to send text through in
|
|
|
|
# chunks as you generate it, use `open` to get a writable object
|
|
|
|
# instead.
|
|
|
|
#
|
|
|
|
# @param [String] text
|
|
|
|
# Text to run through a pager.
|
|
|
|
#
|
|
|
|
def page(text)
|
|
|
|
open do |pager|
|
|
|
|
pager << text
|
|
|
|
end
|
2013-11-02 21:40:56 -04:00
|
|
|
end
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# Yields a pager object (`NullPager`, `SimplePager`, or `SystemPager`).
|
|
|
|
# All pagers accept output with `#puts`, `#print`, `#write`, and `#<<`.
|
|
|
|
def open
|
|
|
|
pager = best_available
|
|
|
|
yield pager
|
2019-03-03 17:05:33 -05:00
|
|
|
rescue StopPaging # rubocop:disable Lint/HandleExceptions
|
2018-11-07 05:49:14 -05:00
|
|
|
ensure
|
|
|
|
pager.close if pager
|
2013-11-02 21:40:56 -04:00
|
|
|
end
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
private
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2019-03-02 08:35:35 -05:00
|
|
|
def enabled?
|
|
|
|
!!@enabled
|
|
|
|
end
|
2018-11-07 05:49:14 -05:00
|
|
|
|
2019-03-02 09:39:17 -05:00
|
|
|
attr_reader :output
|
2018-11-07 05:49:14 -05:00
|
|
|
|
|
|
|
# Return an instance of the "best" available pager class --
|
|
|
|
# `SystemPager` if possible, `SimplePager` if `SystemPager` isn't
|
|
|
|
# available, and `NullPager` if the user has disabled paging. All
|
|
|
|
# pagers accept output with `#puts`, `#print`, `#write`, and `#<<`. You
|
|
|
|
# must call `#close` when you're done writing output to a pager, and
|
|
|
|
# you must rescue `Pry::Pager::StopPaging`. These requirements can be
|
|
|
|
# avoided by using `.open` instead.
|
|
|
|
def best_available
|
|
|
|
if !_pry_.config.pager
|
|
|
|
NullPager.new(_pry_.output)
|
|
|
|
elsif !SystemPager.available? || Helpers::Platform.jruby?
|
|
|
|
SimplePager.new(_pry_.output)
|
|
|
|
else
|
|
|
|
SystemPager.new(_pry_.output)
|
|
|
|
end
|
2013-11-02 21:40:56 -04:00
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# `NullPager` is a "pager" that actually just prints all output as it
|
|
|
|
# comes in. Used when `Pry.config.pager` is false.
|
|
|
|
class NullPager
|
|
|
|
def initialize(out)
|
|
|
|
@out = out
|
|
|
|
end
|
2012-08-08 18:29:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def puts(str)
|
|
|
|
print "#{str.chomp}\n"
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def print(str)
|
|
|
|
write str
|
|
|
|
end
|
|
|
|
alias << print
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def write(str)
|
|
|
|
@out.write str
|
|
|
|
end
|
2012-08-08 18:29:51 -04:00
|
|
|
|
2019-02-28 18:34:50 -05:00
|
|
|
def close; end
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
private
|
2013-10-29 00:25:13 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def height
|
|
|
|
@height ||= Pry::Terminal.height!
|
|
|
|
end
|
2018-10-14 09:44:58 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def width
|
|
|
|
@width ||= Pry::Terminal.width!
|
2012-08-08 18:29:51 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# `SimplePager` is a straightforward pure-Ruby pager. We use it on
|
|
|
|
# JRuby and when we can't find a usable external pager.
|
|
|
|
class SimplePager < NullPager
|
|
|
|
def initialize(*)
|
|
|
|
super
|
|
|
|
@tracker = PageTracker.new(height - 3, width)
|
2013-01-18 20:58:11 -05:00
|
|
|
end
|
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def write(str)
|
|
|
|
str.lines.each do |line|
|
|
|
|
@out.print line
|
|
|
|
@tracker.record line
|
2012-10-26 09:09:07 -04:00
|
|
|
|
2019-03-02 05:02:48 -05:00
|
|
|
next unless @tracker.page?
|
|
|
|
|
|
|
|
@out.print "\n"
|
|
|
|
@out.print "\e[0m"
|
|
|
|
@out.print "<page break> --- Press enter to continue " \
|
|
|
|
"( q<enter> to break ) --- <page break>\n"
|
|
|
|
raise StopPaging if Readline.readline("").chomp == "q"
|
|
|
|
|
|
|
|
@tracker.reset
|
2013-01-18 20:58:11 -05:00
|
|
|
end
|
|
|
|
end
|
2012-10-26 09:09:07 -04:00
|
|
|
end
|
|
|
|
|
2018-11-07 05:49:14 -05: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 < NullPager
|
|
|
|
def self.default_pager
|
|
|
|
pager = ENV["PAGER"] || ""
|
2012-10-26 09:09:07 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# Default to less, and make sure less is being passed the correct
|
|
|
|
# options
|
2019-03-01 19:03:35 -05:00
|
|
|
pager = "less -R -F -X" if pager.strip.empty? || pager =~ /^less\b/
|
2018-11-07 05:49:14 -05:00
|
|
|
|
|
|
|
pager
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
@system_pager = nil
|
|
|
|
|
|
|
|
def self.available?
|
|
|
|
if @system_pager.nil?
|
2019-02-25 17:38:32 -05:00
|
|
|
@system_pager =
|
|
|
|
begin
|
|
|
|
pager_executable = default_pager.split(' ').first
|
|
|
|
if Helpers::Platform.windows? || Helpers::Platform.windows_ansi?
|
|
|
|
`where /Q #{pager_executable}`
|
|
|
|
else
|
|
|
|
`which #{pager_executable}`
|
|
|
|
end
|
2019-03-02 08:59:16 -05:00
|
|
|
$CHILD_STATUS.success?
|
2019-03-02 07:13:59 -05:00
|
|
|
rescue StandardError
|
2019-02-25 17:38:32 -05:00
|
|
|
false
|
2018-11-07 05:49:14 -05:00
|
|
|
end
|
|
|
|
else
|
|
|
|
@system_pager
|
2013-11-02 17:14:51 -04:00
|
|
|
end
|
|
|
|
end
|
2012-10-26 09:09:07 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def initialize(*)
|
|
|
|
super
|
|
|
|
@tracker = PageTracker.new(height, width)
|
|
|
|
@buffer = ""
|
|
|
|
@pager = nil
|
2012-08-08 17:57:19 -04:00
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def write(str)
|
|
|
|
if invoked_pager?
|
|
|
|
write_to_pager str
|
|
|
|
else
|
|
|
|
@tracker.record str
|
|
|
|
@buffer << str
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2019-03-01 19:03:35 -05:00
|
|
|
write_to_pager @buffer if @tracker.page?
|
2018-11-07 05:49:14 -05:00
|
|
|
end
|
|
|
|
rescue Errno::EPIPE
|
|
|
|
raise StopPaging
|
|
|
|
end
|
2014-05-01 04:39:12 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def close
|
|
|
|
if invoked_pager?
|
|
|
|
pager.close
|
|
|
|
else
|
|
|
|
@out.puts @buffer
|
|
|
|
end
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
private
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def write_to_pager(text)
|
|
|
|
pager.write @out.decolorize_maybe(text)
|
|
|
|
end
|
|
|
|
|
|
|
|
def invoked_pager?
|
|
|
|
@pager
|
|
|
|
end
|
|
|
|
|
|
|
|
def pager
|
|
|
|
@pager ||= IO.popen(self.class.default_pager, 'w')
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
end
|
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# `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)
|
2019-03-02 06:21:04 -05:00
|
|
|
@rows = rows
|
|
|
|
@cols = cols
|
2018-11-07 05:49:14 -05:00
|
|
|
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)
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def page?
|
|
|
|
@row >= @rows
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
def reset
|
|
|
|
@row = 0
|
|
|
|
@col = 0
|
|
|
|
end
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
private
|
2013-11-02 17:14:51 -04:00
|
|
|
|
2018-11-07 05:49:14 -05:00
|
|
|
# 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
|
|
|
|
end
|
2012-08-08 17:57:19 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|