From 77a601e16bc381f839decba59a0b962a942ecc74 Mon Sep 17 00:00:00 2001 From: tompng Date: Wed, 31 Jan 2024 15:22:35 +0900 Subject: [PATCH] Fast preview before pager --- lib/irb.rb | 81 +++++++++++++++++++++++++++---------------- lib/irb/context.rb | 4 +-- lib/irb/inspector.rb | 9 ++--- lib/irb/pager.rb | 82 +++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 136 insertions(+), 40 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index f5a875d08..5ff580bad 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -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 diff --git a/lib/irb/context.rb b/lib/irb/context.rb index c65628192..5ace09060 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -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: diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 8046744f8..1c06c287f 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -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}" @@ -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 diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb index 7c1249dd5..00ccaf42d 100644 --- a/lib/irb/pager.rb +++ b/lib/irb/pager.rb @@ -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) @@ -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 @@ -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:) @@ -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