summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/irb.rb58
-rw-r--r--lib/irb/command/copy.rb2
-rw-r--r--lib/irb/context.rb8
-rw-r--r--lib/irb/inspector.rb13
-rw-r--r--lib/irb/pager.rb122
5 files changed, 159 insertions, 44 deletions
diff --git a/lib/irb.rb b/lib/irb.rb
index 80cfa6cd8c..fd0bfe35c7 100644
--- a/lib/irb.rb
+++ b/lib/irb.rb
@@ -516,40 +516,36 @@ module IRB
end
def output_value(omit = false) # :nodoc:
- str = @context.inspect_last_value
- multiline_p = str.include?("\n")
- if omit
- winwidth = @context.io.winsize.last
- if multiline_p
- first_line = str.split("\n").first
- result = @context.newline_before_multiline_output? ? (@context.return_format % first_line) : first_line
- output_width = Reline::Unicode.calculate_width(result, true)
- diff_size = output_width - Reline::Unicode.calculate_width(first_line, true)
- if diff_size.positive? and output_width > winwidth
- lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3)
- str = "%s..." % lines.first
- str += "\e[0m" if Color.colorable?
- multiline_p = false
- else
- str = str.gsub(/(\A.*?\n).*/m, "\\1...")
- str += "\e[0m" if Color.colorable?
- end
- else
- output_width = Reline::Unicode.calculate_width(@context.return_format % str, true)
- diff_size = output_width - Reline::Unicode.calculate_width(str, true)
- if diff_size.positive? and output_width > winwidth
- lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3)
- str = "%s..." % lines.first
- str += "\e[0m" if Color.colorable?
- end
- end
+ unless @context.return_format.include?('%')
+ puts @context.return_format
+ return
end
- if multiline_p && @context.newline_before_multiline_output?
- str = "\n" + str
+ winheight, winwidth = @context.io.winsize
+ if omit
+ content, overflow = Pager.take_first_page(winwidth, 1) do |out|
+ @context.inspect_last_value(out)
+ end
+ if overflow
+ content = "\n#{content}" if @context.newline_before_multiline_output?
+ content = "#{content}..."
+ content = "#{content}\e[0m" if Color.colorable?
+ end
+ puts format(@context.return_format, content.chomp)
+ elsif Pager.should_page? && @context.inspector_support_stream_output?
+ formatter_proc = ->(content, multipage) do
+ content = content.chomp
+ content = "\n#{content}" if @context.newline_before_multiline_output? && (multipage || content.include?("\n"))
+ format(@context.return_format, content)
+ end
+ Pager.page_with_preview(winwidth, winheight, formatter_proc) do |out|
+ @context.inspect_last_value(out)
+ end
+ else
+ content = @context.inspect_last_value.chomp
+ content = "\n#{content}" if @context.newline_before_multiline_output? && content.include?("\n")
+ Pager.page_content(format(@context.return_format, content), retain_content: true)
end
-
- Pager.page_content(format(@context.return_format, str), retain_content: true)
end
# Outputs the local variables to this current session, including #signal_status
diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb
index 3fd3f54935..b6aee0c338 100644
--- a/lib/irb/command/copy.rb
+++ b/lib/irb/command/copy.rb
@@ -15,7 +15,7 @@ module IRB
arg = '_' if arg.to_s.strip.empty?
value = irb_context.workspace.binding.eval(arg)
- output = irb_context.inspect_method.inspect_value(value, colorize: false)
+ output = irb_context.inspect_method.inspect_value(value, +'', colorize: false).chomp
if clipboard_available?
copy_to_clipboard(output)
diff --git a/lib/irb/context.rb b/lib/irb/context.rb
index 2642256c40..395d9081f8 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -658,8 +658,12 @@ module IRB
end
end
- def inspect_last_value # :nodoc:
- @inspect_method.inspect_value(@last_value)
+ def inspect_last_value(output = +'') # :nodoc:
+ @inspect_method.inspect_value(@last_value, output)
+ end
+
+ def inspector_support_stream_output?
+ @inspect_method.support_stream_output?
end
NOPRINTING_IVARS = ["@last_value"] # :nodoc:
diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb
index a1679b20f9..75a257b4ba 100644
--- a/lib/irb/inspector.rb
+++ b/lib/irb/inspector.rb
@@ -92,9 +92,14 @@ module IRB # :nodoc:
@init.call if @init
end
+ def support_stream_output?
+ second_parameter_type = @inspect.parameters[1]&.first
+ second_parameter_type == :req || second_parameter_type == :opt
+ end
+
# Proc to call when the input is evaluated and output in irb.
- def inspect_value(v, colorize: true)
- @inspect.call(v, colorize: colorize)
+ def inspect_value(v, output, colorize: true)
+ support_stream_output? ? @inspect.call(v, output, colorize: colorize) : output << @inspect.call(v, colorize: colorize)
rescue => e
puts "An error occurred when inspecting the object: #{e.inspect}"
@@ -113,8 +118,8 @@ module IRB # :nodoc:
Inspector.def_inspector([:p, :inspect]){|v, colorize: true|
Color.colorize_code(v.inspect, colorable: colorize && Color.colorable? && Color.inspect_colorable?(v))
}
- Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, colorize: true|
- IRB::ColorPrinter.pp(v, +'', colorize: colorize).chomp
+ Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output, colorize: true|
+ IRB::ColorPrinter.pp(v, output, colorize: colorize)
}
Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
begin
diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb
index 7c1249dd5c..65303e5ac1 100644
--- a/lib/irb/pager.rb
+++ b/lib/irb/pager.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'reline'
+
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.
@@ -47,12 +49,42 @@ module IRB
rescue Errno::EPIPE
end
- private
-
def should_page?
IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb")
end
+ def page_with_preview(width, height, formatter_proc)
+ overflow_callback = ->(lines) do
+ modified_output = formatter_proc.call(lines.join, true)
+ content, = take_first_page(width, [height - 2, 0].max) {|o| o.write modified_output }
+ content = content.chomp
+ content = "#{content}\e[0m" if Color.colorable?
+ $stdout.puts content
+ $stdout.puts 'Preparing full inspection value...'
+ end
+ out = PageOverflowIO.new(width, height, overflow_callback, delay: 0.1)
+ yield out
+ content = formatter_proc.call(out.string, out.multipage?)
+ if out.multipage?
+ page(retain_content: true) do |io|
+ io.puts content
+ end
+ else
+ $stdout.puts content
+ end
+ end
+
+ def take_first_page(width, height)
+ overflow_callback = proc do |lines|
+ return lines.join, true
+ end
+ out = Pager::PageOverflowIO.new(width, height, overflow_callback)
+ yield out
+ [out.string, false]
+ end
+
+ private
+
def content_exceeds_screen_height?(content)
screen_height, screen_width = begin
Reline.get_screen_size
@@ -62,10 +94,10 @@ module IRB
pageable_height = screen_height - 3 # leave some space for previous and the current prompt
- # If the content has more lines than the pageable height
- content.lines.count > pageable_height ||
- # Or if the content is a few long lines
- pageable_height * screen_width < Reline::Unicode.calculate_width(content, true)
+ return true if content.lines.size > pageable_height
+
+ _, overflow = take_first_page(screen_width, pageable_height) {|out| out.write content }
+ overflow
end
def setup_pager(retain_content:)
@@ -96,5 +128,83 @@ module IRB
nil
end
end
+
+ # Writable IO that has page overflow callback
+ class PageOverflowIO
+ attr_reader :string, :first_page_lines
+
+ # Maximum size of a single cell in terminal
+ # Assumed worst case: "\e[1;3;4;9;38;2;255;128;128;48;2;128;128;255mA\e[0m"
+ # bold, italic, underline, crossed_out, RGB forgound, RGB background
+ MAX_CHAR_PER_CELL = 50
+
+ def initialize(width, height, overflow_callback, delay: nil)
+ @lines = []
+ @first_page_lines = nil
+ @width = width
+ @height = height
+ @buffer = +''
+ @overflow_callback = overflow_callback
+ @col = 0
+ @string = +''
+ @multipage = false
+ @delay_until = (Time.now + delay if delay)
+ end
+
+ def puts(text = '')
+ write(text)
+ write("\n") unless text.end_with?("\n")
+ end
+
+ def write(text)
+ @string << text
+ if @multipage
+ if @delay_until && Time.now > @delay_until
+ @overflow_callback.call(@first_page_lines)
+ @delay_until = nil
+ end
+ return
+ end
+
+ overflow_size = (@width * (@height - @lines.size) + @width - @col) * MAX_CHAR_PER_CELL
+ if text.size >= overflow_size
+ text = text[0, overflow_size]
+ overflow = true
+ end
+
+ @buffer << text
+ @col += Reline::Unicode.calculate_width(text)
+ if text.include?("\n") || @col >= @width
+ @buffer.lines.each do |line|
+ wrapped_lines = Reline::Unicode.split_by_width(line.chomp, @width).first.compact
+ wrapped_lines.pop if wrapped_lines.last == ''
+ @lines.concat(wrapped_lines)
+ if @lines.empty?
+ @lines << "\n"
+ elsif line.end_with?("\n")
+ @lines[-1] += "\n"
+ end
+ end
+ @buffer.clear
+ @buffer << @lines.pop unless @lines.last.end_with?("\n")
+ @col = Reline::Unicode.calculate_width(@buffer)
+ end
+ if overflow || @lines.size > @height || (@lines.size == @height && @col > 0)
+ @first_page_lines = @lines.take(@height)
+ if !@delay_until || Time.now > @delay_until
+ @overflow_callback.call(@first_page_lines)
+ @delay_until = nil
+ end
+ @multipage = true
+ end
+ end
+
+ def multipage?
+ @multipage
+ end
+
+ alias print write
+ alias << write
+ end
end
end