diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 5dfe9d0d7..22d29314a 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -61,6 +61,8 @@ def initialize(irb, workspace = nil, input_method = nil) @io = nil self.inspect_mode = IRB.conf[:INSPECT_MODE] + @inspect_coloring_timeout = IRB.conf[:INSPECT_COLORING_TIMEOUT] + self.use_tracer = IRB.conf[:USE_TRACER] if IRB.conf[:USE_TRACER] self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER] self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY] @@ -251,6 +253,8 @@ def main attr_reader :use_autocomplete # A copy of the default IRB.conf[:INSPECT_MODE] attr_reader :inspect_mode + # Timeout (in seconds) for inspect coloring when INSPECT_MODE is :pp or :pretty_inspect. + attr_reader :inspect_coloring_timeout # A copy of the default IRB.conf[:PROMPT_MODE] attr_reader :prompt_mode diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index ee3b19efd..2faeab664 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -112,8 +112,18 @@ 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 "timeout"; require_relative "color_printer"; require_relative "pp_printer"} + ){|v| + begin + Timeout.timeout(IRB.conf.fetch(:INSPECT_COLORING_TIMEOUT, 2)) do + IRB::ColorPrinter.pp(v, '').chomp + end + rescue Timeout::Error + # Fall back to non-colored pp if coloring takes too long + IRB::PpPrinter.pp(v, '').chomp + end } Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| begin diff --git a/lib/irb/pp_printer.rb b/lib/irb/pp_printer.rb new file mode 100644 index 000000000..5f758490b --- /dev/null +++ b/lib/irb/pp_printer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true +require 'pp' + +module IRB + class PpPrinter < ::PP + METHOD_RESPOND_TO = Object.instance_method(:respond_to?) + METHOD_INSPECT = Object.instance_method(:inspect) + + class << self + def pp(obj, out = $>, width = screen_width) + q = PpPrinter.new(out, width) + q.guard_inspect_key {q.pp obj} + q.flush + out << "\n" + end + + private + + def screen_width + Reline.get_screen_size.last + rescue Errno::EINVAL # in `winsize': Invalid argument - + 79 + end + end + + def pp(obj) + if String === obj + # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" + obj.inspect + elsif !METHOD_RESPOND_TO.bind(obj).call(:inspect) + text(METHOD_INSPECT.bind(obj).call) + else + super + end + end + end +end