Skip to content

Commit

Permalink
Fast preview before pager
Browse files Browse the repository at this point in the history
  • Loading branch information
tompng committed Nov 29, 2024
1 parent 4217a46 commit 77a601e
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 40 deletions.
81 changes: 52 additions & 29 deletions lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1369,40 +1369,63 @@ def signal_status(status)
end

def output_value(omit = false) # :nodoc:
str = @context.inspect_last_value
multiline_p = str.include?("\n")
return_format = @context.return_format
unless return_format.include?('%')
puts return_format
return
end
unless return_format.include?('%s')
puts return_format % @context.inspect_last_value.chomp
return
end

return_format_pre, return_format_post = return_format.split('%s', 2)

winheight, winwidth = @context.io.winsize
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?
out = IRB::Pager::PagingIO.new(winwidth, 2) do |lines|
puts return_format_pre if @context.newline_before_multiline_output?
omission = "\e[0m" if Color.colorable?
puts "#{lines.first}...#{omission}#{return_format_post}"
return
end
out << return_format_pre unless @context.newline_before_multiline_output?
@context.inspect_last_value(out)
unless out.multipage?
print return_format_pre if @context.newline_before_multiline_output?
puts out.string.chomp + return_format_post
end
elsif !IRB::Pager.should_page?
puts return_format % @context.inspect_last_value.chomp
else
out = IRB::Pager::PagingIO.new(winwidth, winheight - (@context.newline_before_multiline_output? ? 1 : 0)) do |lines|
if @context.newline_before_multiline_output?
lines = [return_format_pre + "\n", *lines]
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?
puts lines.take(winheight - 1).join
print "\e[0mPress SPACE key to show full content:"
unless STDIN.raw { |io| io.readpartial(1024) }&.start_with?(' ')
print "\r\e[K"
return
end
print "\r\e[KPreparing content..."
end
out << return_format_pre unless @context.newline_before_multiline_output?
@context.inspect_last_value(out)
content = out.string
if @context.newline_before_multiline_output?
content = return_format_pre + (out.multiline? ? "\n" : '') + content
end
content = content.chomp + return_format_post
if out.multipage?
print "\r\e[K"
Pager.page(retain_content: true) do |io|
io.puts content
end
else
puts content
end
end

if multiline_p && @context.newline_before_multiline_output?
str = "\n" + str
end

Pager.page_content(format(@context.return_format, str), retain_content: true)
end

# Outputs the local variables to this current session, including #signal_status
Expand Down
4 changes: 2 additions & 2 deletions lib/irb/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -673,8 +673,8 @@ def colorize_input(input, complete:)
end
end

def inspect_last_value # :nodoc:
@inspect_method.inspect_value(@last_value)
def inspect_last_value(output = nil) # :nodoc:
@inspect_method.inspect_value(@last_value, output)
end

NOPRINTING_IVARS = ["@last_value"] # :nodoc:
Expand Down
9 changes: 5 additions & 4 deletions lib/irb/inspector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ def init
end

# Proc to call when the input is evaluated and output in irb.
def inspect_value(v)
@inspect.call(v)
def inspect_value(v, output)
@inspect.arity == 2 ? @inspect.call(v, output) : output.write(@inspect.call(v))
rescue IRB::Pager::Abort
rescue => e
puts "An error occurred when inspecting the object: #{e.inspect}"

Expand All @@ -113,8 +114,8 @@ def inspect_value(v)
Inspector.def_inspector([:p, :inspect]){|v|
Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v))
}
Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v|
IRB::ColorPrinter.pp(v, +'').chomp
Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v, output|
IRB::ColorPrinter.pp(v, output)
}
Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
begin
Expand Down
82 changes: 77 additions & 5 deletions lib/irb/pager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ module IRB
class Pager
PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq

# Maximum size of a single cell in terminal
# assuming the worst case: bold, italic, underline, reverse, RGB forgound and background color
# "\e[1;3;4;7;38;2;255;128;128;48;2;128;128;255mA\e[0m"
MAX_CHAR_PER_CELL = 50

class << self
def page_content(content, **options)
if content_exceeds_screen_height?(content)
Expand Down Expand Up @@ -47,12 +52,12 @@ def page(retain_content: false)
rescue Errno::EPIPE
end

private

def should_page?
IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb")
end

private

def content_exceeds_screen_height?(content)
screen_height, screen_width = begin
Reline.get_screen_size
Expand All @@ -63,9 +68,9 @@ def content_exceeds_screen_height?(content)
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)
content.lines.count > pageable_height || content.size >= pageable_height * screen_width * MAX_CHAR_PER_CELL
# Or if the content is a few long lines
pageable_height * screen_width < Reline::Unicode.calculate_width(content, true)
end

def setup_pager(retain_content:)
Expand Down Expand Up @@ -96,5 +101,72 @@ def setup_pager(retain_content:)
nil
end
end

class PagingIO
attr_reader :string
def initialize(width, height, &block)
@lines = []
@width = width
@height = height
@buffer = +''
@block = block
@col = 0
@string = +''
@multipage = false
end

def puts(text = '')
write(text)
write("\n") unless text.end_with?("\n")
end

def write(text)
@string << text
return if @multipage

overflow_size = @width * @height * 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
@block.call(@lines)
@multipage = true
end
end

def finish
@block.call(@lines) unless @multipage
end

def multipage?
@multipage
end

def multiline?
@lines.size >= 2
end

alias print write
alias << write
end
end
end

0 comments on commit 77a601e

Please sign in to comment.