1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

pager.rb: fix Helpers constant invocation

Since the top class is Pry::Pager and not Pry, the cref constant static lookup
only look for constants under Pry::Pager (or at the top level).

Since Helpers refers to Pry::Helpers, it won't be found. Fix this by
changing the top class to Pry and put the class Pager in the Pry
namespace.

This introduce a two space shift of all the code, so reformat the
comments.

This also fixes the following bug: in SystemPager.available? the Helpers
constant lookup fails, so this raises an exception which is rescued and
then returns false; so the SimplePager is always chosen instead.
This commit is contained in:
Damien Robert 2018-11-07 11:49:14 +01:00
parent 7209409091
commit bd07d11c1b

View file

@ -3,241 +3,248 @@ require 'pry/terminal'
# A pager is an `IO`-like object that accepts text and either prints it # A 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 # immediately, prints it one page at a time, or streams it to an external
# program to print one page at a time. # program to print one page at a time.
class Pry::Pager class Pry
class StopPaging < StandardError class Pager
end class StopPaging < StandardError
attr_reader :_pry_
def initialize(_pry_)
@_pry_ = _pry_
end
# 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
end
# Yields a pager object (`NullPager`, `SimplePager`, or `SystemPager`). All
# pagers accept output with `#puts`, `#print`, `#write`, and `#<<`.
def open
pager = best_available
yield pager
rescue StopPaging
ensure
pager.close if pager
end
private
def enabled?; !!@enabled; end
def output; @output; end
# 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
end
# `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 end
def puts(str) attr_reader :_pry_
print "#{str.chomp}\n"
def initialize(_pry_)
@_pry_ = _pry_
end end
def print(str) # Send the given text through the best available pager (if
write str # `Pry.config.pager` is enabled). If you want to send text through in
end # chunks as you generate it, use `open` to get a writable object
alias << print # instead.
#
def write(str) # @param [String] text
@out.write str # Text to run through a pager.
#
def page(text)
open do |pager|
pager << text
end
end end
def close # Yields a pager object (`NullPager`, `SimplePager`, or `SystemPager`).
# All pagers accept output with `#puts`, `#print`, `#write`, and `#<<`.
def open
pager = best_available
yield pager
rescue StopPaging
ensure
pager.close if pager
end end
private private
def height def enabled?; !!@enabled; end
@height ||= Pry::Terminal.height!
end
def width def output; @output; end
@width ||= Pry::Terminal.width!
end
end
# `SimplePager` is a straightforward pure-Ruby pager. We use it on JRuby and # Return an instance of the "best" available pager class --
# when we can't find a usable external pager. # `SystemPager` if possible, `SimplePager` if `SystemPager` isn't
class SimplePager < NullPager # available, and `NullPager` if the user has disabled paging. All
def initialize(*) # pagers accept output with `#puts`, `#print`, `#write`, and `#<<`. You
super # must call `#close` when you're done writing output to a pager, and
@tracker = PageTracker.new(height - 3, width) # you must rescue `Pry::Pager::StopPaging`. These requirements can be
end # avoided by using `.open` instead.
def best_available
def write(str) if !_pry_.config.pager
str.lines.each do |line| NullPager.new(_pry_.output)
@out.print line elsif !SystemPager.available? || Helpers::Platform.jruby?
@tracker.record line SimplePager.new(_pry_.output)
else
if @tracker.page? SystemPager.new(_pry_.output)
@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
end
end end
end end
end
# `SystemPager` buffers output until we're pretty sure it's at least a page # `NullPager` is a "pager" that actually just prints all output as it
# long, then invokes an external pager and starts streaming output to it. If # comes in. Used when `Pry.config.pager` is false.
# `#close` is called before then, it just prints out the buffered content. class NullPager
class SystemPager < NullPager def initialize(out)
def self.default_pager @out = out
pager = ENV["PAGER"] || ""
# Default to less, and make sure less is being passed the correct options
if pager.strip.empty? or pager =~ /^less\b/
pager = "less -R -F -X"
end end
pager def puts(str)
print "#{str.chomp}\n"
end
def print(str)
write str
end
alias << print
def write(str)
@out.write str
end
def close
end
private
def height
@height ||= Pry::Terminal.height!
end
def width
@width ||= Pry::Terminal.width!
end
end end
@system_pager = nil # `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)
end
def self.available? def write(str)
if @system_pager.nil? str.lines.each do |line|
@system_pager = begin @out.print line
pager_executable = default_pager.split(' ').first @tracker.record line
if Helpers::Platform.windows? || Helpers::Platform.windows_ansi?
`where /Q #{pager_executable}` if @tracker.page?
else @out.print "\n"
`which #{pager_executable}` @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
end end
$?.success?
rescue
false
end
else
@system_pager
end
end
def initialize(*)
super
@tracker = PageTracker.new(height, width)
@buffer = ""
@pager = nil
end
def write(str)
if invoked_pager?
write_to_pager str
else
@tracker.record str
@buffer << str
if @tracker.page?
write_to_pager @buffer
end end
end end
rescue Errno::EPIPE
raise StopPaging
end end
def close # `SystemPager` buffers output until we're pretty sure it's at least a
if invoked_pager? # page long, then invokes an external pager and starts streaming output
pager.close # to it. If `#close` is called before then, it just prints out the
else # buffered content.
@out.puts @buffer class SystemPager < NullPager
def self.default_pager
pager = ENV["PAGER"] || ""
# Default to less, and make sure less is being passed the correct
# options
if pager.strip.empty? or pager =~ /^less\b/
pager = "less -R -F -X"
end
pager
end end
end
private @system_pager = nil
def write_to_pager(text) def self.available?
pager.write @out.decolorize_maybe(text) if @system_pager.nil?
end @system_pager = begin
pager_executable = default_pager.split(' ').first
def invoked_pager? if Helpers::Platform.windows? || Helpers::Platform.windows_ansi?
@pager `where /Q #{pager_executable}`
end else
`which #{pager_executable}`
def pager end
@pager ||= IO.popen(self.class.default_pager, 'w') $?.success?
end rescue
end false
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 else
@col += line_length(line) @system_pager
end end
end end
def initialize(*)
super
@tracker = PageTracker.new(height, width)
@buffer = ""
@pager = nil
end
def write(str)
if invoked_pager?
write_to_pager str
else
@tracker.record str
@buffer << str
if @tracker.page?
write_to_pager @buffer
end
end
rescue Errno::EPIPE
raise StopPaging
end
def close
if invoked_pager?
pager.close
else
@out.puts @buffer
end
end
private
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
end end
def page? # `PageTracker` tracks output to determine whether it's likely to take
@row >= @rows # up a whole page. This doesn't need to be super precise, but we can
end # 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 reset def record(str)
@row = 0 str.lines.each do |line|
@col = 0 if line.end_with? "\n"
end @row += ((@col + line_length(line) - 1) / @cols) + 1
@col = 0
else
@col += line_length(line)
end
end
end
private def page?
@row >= @rows
end
# Approximation of the printable length of a given line, without the def reset
# newline and without ANSI color codes. @row = 0
def line_length(line) @col = 0
line.chomp.gsub(/\e\[[\d;]*m/, '').length 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
end
end end
end end
end end