Skip to content

Commit

Permalink
Centralize coloring control (#374)
Browse files Browse the repository at this point in the history
* Use colorable: argument as the only coloring control

* Centalize color controling logic at Color.colorable?

There are 2 requirements for coloring output:

1. It's supported on the platform
2. The user wants it: `IRB.conf[:USE_COLORIZE] == true`

Right now we check 1 and 2 separately whenever we colorize things.
But it's error-prone because while 1 is the default of `colorable`
parameter, 2 always need to manually checked. When 2 is overlooked, it
causes issues like #362

And there's 0 case where we may want to colorize even when the user
disables it. So I think we should merge 2 into `Color.colorable?` so it
can be automatically picked up.

* Add tests for all inspect modes

* Simplify inspectors' coloring logic

* Replace use_colorize? with Color.colorable?

* Remove Context#use_colorize cause it's redundant
  • Loading branch information
st0012 committed Jun 28, 2022
1 parent 7db93f9 commit 1c53023
Show file tree
Hide file tree
Showing 9 changed files with 46 additions and 58 deletions.
6 changes: 3 additions & 3 deletions lib/irb.rb
Expand Up @@ -828,19 +828,19 @@ def output_value(omit = false) # :nodoc:
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 @context.use_colorize
str += "\e[0m" if Color.colorable?
multiline_p = false
else
str = str.gsub(/(\A.*?\n).*/m, "\\1...")
str += "\e[0m" if @context.use_colorize
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 @context.use_colorize
str += "\e[0m" if Color.colorable?
end
end
end
Expand Down
7 changes: 3 additions & 4 deletions lib/irb/cmd/ls.rb
Expand Up @@ -10,7 +10,7 @@ module IRB
module ExtendCommand
class Ls < Nop
def execute(*arg, grep: nil)
o = Output.new(grep: grep, colorable: colorable)
o = Output.new(grep: grep)

obj = arg.empty? ? irb_context.workspace.main : arg.first
locals = arg.empty? ? irb_context.workspace.binding.local_variables : []
Expand Down Expand Up @@ -45,8 +45,7 @@ def class_method_map(classes)
class Output
MARGIN = " "

def initialize(grep: nil, colorable: true)
@colorable = colorable
def initialize(grep: nil)
@grep = grep
@line_width = screen_width - MARGIN.length # right padding
end
Expand All @@ -57,7 +56,7 @@ def dump(name, strs)
return if strs.empty?

# Attempt a single line
print "#{Color.colorize(name, [:BOLD, :BLUE], colorable: @colorable)}: "
print "#{Color.colorize(name, [:BOLD, :BLUE])}: "
if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length)
puts strs.join(MARGIN)
return
Expand Down
3 changes: 1 addition & 2 deletions lib/irb/cmd/nop.rb
Expand Up @@ -29,10 +29,9 @@ def self.execute(conf, *opts, &block)

def initialize(conf)
@irb_context = conf
@colorable = Color.colorable? && conf.use_colorize
end

attr_reader :irb_context, :colorable
attr_reader :irb_context

def irb
@irb_context.irb
Expand Down
4 changes: 2 additions & 2 deletions lib/irb/cmd/show_source.rb
Expand Up @@ -30,7 +30,7 @@ def show_source(source)
puts
puts "#{bold("From")}: #{source.file}:#{source.first_line}"
puts
code = IRB::Color.colorize_code(File.read(source.file), colorable: colorable)
code = IRB::Color.colorize_code(File.read(source.file))
puts code.lines[(source.first_line - 1)...source.last_line].join
puts
end
Expand Down Expand Up @@ -78,7 +78,7 @@ def find_end(file, first_line)
end

def bold(str)
Color.colorize(str, [:BOLD], colorable: colorable)
Color.colorize(str, [:BOLD])
end

Source = Struct.new(
Expand Down
2 changes: 1 addition & 1 deletion lib/irb/color.rb
Expand Up @@ -77,7 +77,7 @@ module Color

class << self
def colorable?
$stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
$stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) && IRB.conf.fetch(:USE_COLORIZE, true)
end

def inspect_colorable?(obj, seen: {}.compare_by_identity)
Expand Down
5 changes: 0 additions & 5 deletions lib/irb/context.rb
Expand Up @@ -53,7 +53,6 @@ def initialize(irb, workspace = nil, input_method = nil)
else
@use_multiline = nil
end
@use_colorize = IRB.conf[:USE_COLORIZE]
@use_autocomplete = IRB.conf[:USE_AUTOCOMPLETE]
@verbose = IRB.conf[:VERBOSE]
@io = nil
Expand Down Expand Up @@ -186,8 +185,6 @@ def main
attr_reader :use_singleline
# Whether colorization is enabled or not.
#
# A copy of the default <code>IRB.conf[:USE_COLORIZE]</code>
attr_reader :use_colorize
# A copy of the default <code>IRB.conf[:USE_AUTOCOMPLETE]</code>
attr_reader :use_autocomplete
# A copy of the default <code>IRB.conf[:INSPECT_MODE]</code>
Expand Down Expand Up @@ -332,8 +329,6 @@ def main
alias use_readline use_singleline
# backward compatibility
alias use_readline? use_singleline
# Alias for #use_colorize
alias use_colorize? use_colorize
# Alias for #use_autocomplete
alias use_autocomplete? use_autocomplete
# Alias for #rc
Expand Down
12 changes: 2 additions & 10 deletions lib/irb/inspector.rb
Expand Up @@ -108,18 +108,10 @@ def inspect_value(v)

Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s}
Inspector.def_inspector([:p, :inspect]){|v|
result = v.inspect
if IRB.conf[:MAIN_CONTEXT]&.use_colorize? && Color.inspect_colorable?(v)
result = Color.colorize_code(result)
end
result
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|
if IRB.conf[:MAIN_CONTEXT]&.use_colorize?
IRB::ColorPrinter.pp(v, '').chomp
else
v.pretty_inspect.chomp
end
IRB::ColorPrinter.pp(v, '').chomp
}
Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v|
begin
Expand Down
19 changes: 6 additions & 13 deletions lib/irb/workspace.rb
Expand Up @@ -158,27 +158,20 @@ def code_around_binding
end
end

# NOT using #use_colorize? of IRB.conf[:MAIN_CONTEXT] because this method may be called before IRB::Irb#run
use_colorize = IRB.conf.fetch(:USE_COLORIZE, true)
if use_colorize
lines = Color.colorize_code(code).lines
else
lines = code.lines
end
lines = Color.colorize_code(code).lines
pos -= 1

start_pos = [pos - 5, 0].max
end_pos = [pos + 5, lines.size - 1].min

if use_colorize
fmt = " %2s #{Color.colorize("%#{end_pos.to_s.length}d", [:BLUE, :BOLD])}: %s"
else
fmt = " %2s %#{end_pos.to_s.length}d: %s"
end
line_number_fmt = Color.colorize("%#{end_pos.to_s.length}d", [:BLUE, :BOLD])
fmt = " %2s #{line_number_fmt}: %s"

body = (start_pos..end_pos).map do |current_pos|
sprintf(fmt, pos == current_pos ? '=>' : '', current_pos + 1, lines[current_pos])
end.join("")
"\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear if use_colorize}\n"

"\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n"
end

def IRB.delete_caller
Expand Down
46 changes: 28 additions & 18 deletions test/irb/test_context.rb
Expand Up @@ -145,30 +145,40 @@ def test_output_to_pipe
assert_equal "=> 1\n", out
end

def test_eval_object_without_inspect_method
verbose, $VERBOSE = $VERBOSE, nil
all_assertions do |all|
IRB::Inspector::INSPECTORS.invert.each_value do |mode|
all.for(mode) do
input = TestInputMethod.new([
"[BasicObject.new, Class.new]\n",
])
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
irb.context.inspect_mode = mode
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(/\(Object doesn't support #inspect\)\n(=> )?\n/, out)
{
successful: [
[false, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/],
[:p, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct bar=123>/],
[true, "class Foo < Struct.new(:bar); end; Foo.new(123)\n", /#<struct #<Class:.*>::Foo bar=123>/],
[:yaml, "123", /--- 123\n/],
[:marshal, "123", Marshal.dump(123)],
],
failed: [
[false, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:p, "class Foo; undef inspect ;end; Foo.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[true, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:yaml, "BasicObject.new", /\(Object doesn't support #inspect\)\n(=> )?\n/],
[:marshal, "[Object.new, Class.new]", /\(Object doesn't support #inspect\)\n(=> )?\n/]
]
}.each do |scenario, cases|
cases.each do |inspect_mode, input, expected|
define_method "test_#{inspect_mode}_inspect_mode_#{scenario}" do
pend if RUBY_ENGINE == 'truffleruby'
verbose, $VERBOSE = $VERBOSE, nil
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), TestInputMethod.new([input]))
irb.context.inspect_mode = inspect_mode
out, err = capture_output do
irb.eval_input
end
assert_empty err
assert_match(expected, out)
ensure
$VERBOSE = verbose
end
end
ensure
$VERBOSE = verbose
end

def test_default_config
assert_equal(true, @context.use_colorize?)
assert_equal(true, @context.use_autocomplete?)
end

Expand Down

0 comments on commit 1c53023

Please sign in to comment.