diff --git a/doc/Configurations.md b/doc/Configurations.md
index 755c88095..a7efbc8a8 100644
--- a/doc/Configurations.md
+++ b/doc/Configurations.md
@@ -31,6 +31,7 @@ Method `conf.rc?` returns `true` if a configuration file was read, `false` other
- `NO_COLOR`: Disables \IRB's colorization.
- `IRB_USE_AUTOCOMPLETE`: Setting to `false` disables autocompletion.
- `IRB_COMPLETOR`: Configures auto-completion behavior (`regexp` or `type`).
+- `IRB_COPY_COMMAND`: Overrides the default program used to interface with the system clipboard.
- `VISUAL` / `EDITOR`: Specifies the editor for the `edit` command.
- `IRBRC`: Specifies the rc-file for configuration.
- `XDG_CONFIG_HOME`: Used to locate the rc-file if `IRBRC` is unset.
diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb
index 6cfaefd05..7a7e81785 100644
--- a/lib/irb/color_printer.rb
+++ b/lib/irb/color_printer.rb
@@ -5,8 +5,8 @@
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 @@ def screen_width
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 @@ def text(str, width = nil)
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 000000000..3fd3f5493
--- /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 d87e8451e..2642256c4 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -264,6 +264,8 @@ def irb_path=(path)
attr_reader :use_autocomplete
# A copy of the default IRB.conf[:INSPECT_MODE]
attr_reader :inspect_mode
+ # Inspector for the current context
+ attr_reader :inspect_method
# A copy of the default IRB.conf[:PROMPT_MODE]
attr_reader :prompt_mode
diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb
index 533bdfc87..9820a1f30 100644
--- a/lib/irb/default_commands.rb
+++ b/lib/irb/default_commands.rb
@@ -9,6 +9,7 @@
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 @@ def load_command(command)
)
register(:cd, Command::CD)
+ register(:copy, Command::Copy)
end
ExtendCommand = Command
diff --git a/lib/irb/init.rb b/lib/irb/init.rb
index b41536e61..720c4fec4 100644
--- a/lib/irb/init.rb
+++ b/lib/irb/init.rb
@@ -194,6 +194,8 @@ def IRB.init_config(ap_path)
:'$' => :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 8046744f8..a1679b20f 100644
--- a/lib/irb/inspector.rb
+++ b/lib/irb/inspector.rb
@@ -93,8 +93,8 @@ 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, colorize: true)
+ @inspect.call(v, colorize: colorize)
rescue => e
puts "An error occurred when inspecting the object: #{e.inspect}"
@@ -110,11 +110,11 @@ def inspect_value(v)
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
diff --git a/man/irb.1 b/man/irb.1
index 90cf5d4ae..48586a3b7 100644
--- a/man/irb.1
+++ b/man/irb.1
@@ -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 000000000..c35df6c3e
--- /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# "1\n",
+ "a\nb" => %["a\\nb"\n],
+ IRBTestColorPrinter.new('test') => "#\n",
+ Ripper::Lexer.new('1').scan => "[#]\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 f7168e02f..f34f692f0 100644
--- a/test/irb/test_init.rb
+++ b/test/irb/test_init.rb
@@ -163,6 +163,26 @@ def test_use_autocomplete_environment_variable
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]