diff options
-rw-r--r-- | lib/irb/color_printer.rb | 14 | ||||
-rw-r--r-- | lib/irb/command/copy.rb | 63 | ||||
-rw-r--r-- | lib/irb/context.rb | 2 | ||||
-rw-r--r-- | lib/irb/default_commands.rb | 2 | ||||
-rw-r--r-- | lib/irb/init.rb | 2 | ||||
-rw-r--r-- | lib/irb/inspector.rb | 12 | ||||
-rw-r--r-- | man/irb.1 | 2 | ||||
-rw-r--r-- | test/irb/command/test_copy.rb | 70 | ||||
-rw-r--r-- | test/irb/test_color_printer.rb | 13 | ||||
-rw-r--r-- | test/irb/test_init.rb | 20 |
10 files changed, 190 insertions, 10 deletions
diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb index 6cfaefd055..7a7e817858 100644 --- a/lib/irb/color_printer.rb +++ b/lib/irb/color_printer.rb @@ -5,8 +5,8 @@ require_relative 'color' module IRB class ColorPrinter < ::PP class << self - def pp(obj, out = $>, width = screen_width) - q = ColorPrinter.new(out, width) + def pp(obj, out = $>, width = screen_width, colorize: true) + q = ColorPrinter.new(out, width, colorize: colorize) q.guard_inspect_key {q.pp obj} q.flush out << "\n" @@ -21,6 +21,12 @@ module IRB end end + def initialize(out, width, colorize: true) + @colorize = colorize + + super(out, width) + end + def pp(obj) if String === obj # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" @@ -41,9 +47,9 @@ module IRB when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ super(str, width) when /\A#</, '=', '>' - super(Color.colorize(str, [:GREEN]), width) + super(@colorize ? Color.colorize(str, [:GREEN]) : str, width) else - super(Color.colorize_code(str, ignore_error: true), width) + super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width) end end end diff --git a/lib/irb/command/copy.rb b/lib/irb/command/copy.rb new file mode 100644 index 0000000000..3fd3f54935 --- /dev/null +++ b/lib/irb/command/copy.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module IRB + module Command + class Copy < Base + category "Workspace" + description "Copy command output to clipboard" + + help_message(<<~HELP) + Usage: copy [command] + HELP + + def execute(arg) + # Copy last value if no expression was supplied + arg = '_' if arg.to_s.strip.empty? + + value = irb_context.workspace.binding.eval(arg) + output = irb_context.inspect_method.inspect_value(value, colorize: false) + + if clipboard_available? + copy_to_clipboard(output) + else + warn "System clipboard not found" + end + rescue StandardError => e + warn "Error: #{e}" + end + + private + + def copy_to_clipboard(text) + IO.popen(clipboard_program, 'w') do |io| + io.write(text) + end + + raise IOError.new("Copying to clipboard failed") unless $? == 0 + + puts "Copied to system clipboard" + rescue Errno::ENOENT => e + warn e.message + warn "Is IRB.conf[:COPY_COMMAND] set to a bad value?" + end + + def clipboard_program + @clipboard_program ||= if IRB.conf[:COPY_COMMAND] + IRB.conf[:COPY_COMMAND] + elsif executable?("pbcopy") + "pbcopy" + elsif executable?("xclip") + "xclip -selection clipboard" + end + end + + def executable?(command) + system("which #{command} > /dev/null 2>&1") + end + + def clipboard_available? + !!clipboard_program + end + end + end +end diff --git a/lib/irb/context.rb b/lib/irb/context.rb index d87e8451e9..2642256c40 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -264,6 +264,8 @@ module IRB attr_reader :use_autocomplete # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code> attr_reader :inspect_mode + # Inspector for the current context + attr_reader :inspect_method # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code> attr_reader :prompt_mode diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb index 533bdfc875..9820a1f304 100644 --- a/lib/irb/default_commands.rb +++ b/lib/irb/default_commands.rb @@ -9,6 +9,7 @@ require_relative "command/cd" require_relative "command/chws" require_relative "command/context" require_relative "command/continue" +require_relative "command/copy" require_relative "command/debug" require_relative "command/delete" require_relative "command/disable_irb" @@ -250,6 +251,7 @@ module IRB ) register(:cd, Command::CD) + register(:copy, Command::Copy) end ExtendCommand = Command diff --git a/lib/irb/init.rb b/lib/irb/init.rb index b41536e61a..720c4fec46 100644 --- a/lib/irb/init.rb +++ b/lib/irb/init.rb @@ -194,6 +194,8 @@ module IRB # :nodoc: :'$' => :show_source, :'@' => :whereami, } + + @CONF[:COPY_COMMAND] = ENV.fetch("IRB_COPY_COMMAND", nil) end def IRB.set_measure_callback(type = nil, arg = nil, &block) diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb index 8046744f88..a1679b20f9 100644 --- a/lib/irb/inspector.rb +++ b/lib/irb/inspector.rb @@ -93,8 +93,8 @@ module IRB # :nodoc: end # Proc to call when the input is evaluated and output in irb. - def inspect_value(v) - @inspect.call(v) + def inspect_value(v, colorize: true) + @inspect.call(v, colorize: colorize) rescue => e puts "An error occurred when inspecting the object: #{e.inspect}" @@ -110,11 +110,11 @@ module IRB # :nodoc: end Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} - Inspector.def_inspector([:p, :inspect]){|v| - Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v)) + 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| - IRB::ColorPrinter.pp(v, +'').chomp + 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([:yaml, :YAML], proc{require "yaml"}){|v| begin @@ -227,6 +227,8 @@ or .Sy type . .Pp +.It Ev IRB_COPY_COMMAND +Overrides the default program used to interface with the system clipboard. .El .Pp Also diff --git a/test/irb/command/test_copy.rb b/test/irb/command/test_copy.rb new file mode 100644 index 0000000000..8ba62bd968 --- /dev/null +++ b/test/irb/command/test_copy.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'irb' + +require_relative "../helper" + +module TestIRB + class CopyTest < IntegrationTestCase + def setup + super + @envs['IRB_COPY_COMMAND'] = "ruby -e \"puts 'foo' + STDIN.read\"" + end + + def test_copy_with_pbcopy + write_ruby <<~'ruby' + class Answer + def initialize(answer) + @answer = answer + end + end + + binding.irb + ruby + + output = run_ruby_file do + type "copy Answer.new(42)" + type "exit" + end + + assert_match(/foo#<Answer:0x[0-9a-f]+ @answer=42/, output) + assert_match(/Copied to system clipboard/, output) + end + + # copy puts 5 should: + # - Print value to the console + # - Copy nil to clipboard, since that is what the puts call evaluates to + def test_copy_when_expression_has_side_effects + write_ruby <<~'ruby' + binding.irb + ruby + + output = run_ruby_file do + type "copy puts 42" + type "exit" + end + + assert_match(/^42\r\n/, output) + assert_match(/foonil/, output) + assert_match(/Copied to system clipboard/, output) + refute_match(/foo42/, output) + end + + def test_copy_when_copy_command_is_invalid + @envs['IRB_COPY_COMMAND'] = "lulz" + + write_ruby <<~'ruby' + binding.irb + ruby + + output = run_ruby_file do + type "copy 42" + type "exit" + end + + assert_match(/No such file or directory - lulz/, output) + assert_match(/Is IRB\.conf\[:COPY_COMMAND\] set to a bad value/, output) + refute_match(/Copied to system clipboard/, output) + end + end +end diff --git a/test/irb/test_color_printer.rb b/test/irb/test_color_printer.rb index c2c624d868..95d08e19e3 100644 --- a/test/irb/test_color_printer.rb +++ b/test/irb/test_color_printer.rb @@ -49,6 +49,19 @@ module TestIRB end end + def test_colorization_disabled + { + 1 => "1\n", + "a\nb" => %["a\\nb"\n], + IRBTestColorPrinter.new('test') => "#<struct TestIRB::ColorPrinterTest::IRBTestColorPrinter a=\"test\">\n", + Ripper::Lexer.new('1').scan => "[#<Ripper::Lexer::Elem: on_int@1:0 END token: \"1\">]\n", + Class.new{define_method(:pretty_print){|q| q.text("[__FILE__, __LINE__, __ENCODING__]")}}.new => "[__FILE__, __LINE__, __ENCODING__]\n", + }.each do |object, result| + actual = with_term { IRB::ColorPrinter.pp(object, '', colorize: false) } + assert_equal(result, actual, "Case: IRB::ColorPrinter.pp(#{object.inspect}, '')") + end + end + private def with_term diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index f7168e02fe..f34f692f09 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -163,6 +163,26 @@ module TestIRB IRB.conf[:USE_AUTOCOMPLETE] = orig_use_autocomplete_conf end + def test_copy_command_environment_variable + orig_copy_command_env = ENV['IRB_COPY_COMMAND'] + orig_copy_command_conf = IRB.conf[:COPY_COMMAND] + + ENV['IRB_COPY_COMMAND'] = nil + IRB.setup(__FILE__) + refute IRB.conf[:COPY_COMMAND] + + ENV['IRB_COPY_COMMAND'] = '' + IRB.setup(__FILE__) + assert_equal('', IRB.conf[:COPY_COMMAND]) + + ENV['IRB_COPY_COMMAND'] = 'blah' + IRB.setup(__FILE__) + assert_equal('blah', IRB.conf[:COPY_COMMAND]) + ensure + ENV['IRB_COPY_COMMAND'] = orig_copy_command_env + IRB.conf[:COPY_COMMAND] = orig_copy_command_conf + end + def test_completor_environment_variable orig_use_autocomplete_env = ENV['IRB_COMPLETOR'] orig_use_autocomplete_conf = IRB.conf[:COMPLETOR] |