diff options
author | tomoya ishida <[email protected]> | 2025-01-21 22:58:05 +0900 |
---|---|---|
committer | tomoya ishida <[email protected]> | 2025-01-22 23:08:34 +0900 |
commit | 7813edbe19a2cd131aec5490a8ad314ded61e70e (patch) | |
tree | 6c5cf79c1c39db6a89a8688a030f692e3d8f149e /lib | |
parent | c6e8ee4514c8f24c211a5fb0b6291ac81ee83115 (diff) |
[ruby/irb] Show a quick preview of inspect result before pager
launch
(https://github.com/ruby/irb/pull/1040)
* Quickly show inspect preview even if pretty_print takes too much time
* Show a message "Inspecting..." while generating pretty_print content
* Update inspecting message
Co-authored-by: Stan Lo <[email protected]>
* Update rendering test for preparing inspect message
* Don't show preview if pretty_print does not take time
---------
https://github.com/ruby/irb/commit/03c36586e6
Co-authored-by: Stan Lo <[email protected]>
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/12612
Diffstat (limited to 'lib')
-rw-r--r-- | lib/irb.rb | 58 | ||||
-rw-r--r-- | lib/irb/command/copy.rb | 2 | ||||
-rw-r--r-- | lib/irb/context.rb | 8 | ||||
-rw-r--r-- | lib/irb/inspector.rb | 13 | ||||
-rw-r--r-- | lib/irb/pager.rb | 122 |
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 |