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