Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 45 additions & 4 deletions lib/irb/color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ module Color
__END__: [GREEN],
# tokens from syntax tree traversal
method_name: [BLUE, BOLD],
message_name: [BLUE],
symbol: [YELLOW],
# special colorization
error: [RED, REVERSE],
Expand All @@ -111,7 +112,11 @@ module Color
styles.map { |style| "\e[#{style}m" }.join
end
CLEAR_SEQ = "\e[#{CLEAR}m"
private_constant :TOKEN_SEQS, :CLEAR_SEQ
OPERATORS = [
:!=, :!~, :=~, :==, :===, :<=>, :>, :>=, :<, :<=, :&, :|, :^, :>>, :<<, :-, :+, :%, :/, :*, :**,
:-@, :+@, :~, :!, :[], :[]=
]
private_constant :TOKEN_SEQS, :CLEAR_SEQ, :OPERATORS

class << self
def colorable?
Expand Down Expand Up @@ -160,7 +165,7 @@ def colorize(text, seq, colorable: colorable?)
# If `complete` is false (code is incomplete), this does not warn compile_error.
# This option is needed to avoid warning a user when the compile_error is happening
# because the input is not wrong but just incomplete.
def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: [])
def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, colorize_call: true, local_variables: [])
return code unless colorable

result = Prism.parse_lex(code, scopes: [local_variables])
Expand All @@ -175,7 +180,7 @@ def colorize_code(code, complete: true, ignore_error: false, colorable: colorabl
errors = filter_incomplete_code_errors(errors, prism_tokens)
end

visitor = ColorizeVisitor.new
visitor = ColorizeVisitor.new(colorize_call: colorize_call)
prism_node.accept(visitor)

error_tokens = errors.map { |e| [e.location.start_line, e.location.start_column, 0, e.location.end_line, e.location.end_column, :error, e.location.slice] }
Expand Down Expand Up @@ -229,7 +234,8 @@ def colorize_code(code, complete: true, ignore_error: false, colorable: colorabl

class ColorizeVisitor < Prism::Visitor
attr_reader :tokens
def initialize
def initialize(colorize_call: true)
@colorize_call = colorize_call
@tokens = []
end

Expand All @@ -252,6 +258,26 @@ def visit_def_node(node)
super
end

def visit_call_node(node)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Writer methods are also colored blue, but Writer methods with operators are not colored.

# "foo" is colored
a.foo = 1
# "foo" is not colored
a.foo += 1
a.foo &&= 1
a.foo ||= 1

So I think we can also add visitor methods for CallAndWriteNode CallOperatorWriteNode CallOrWriteNode.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed it in 66933a9.

colorize_call(node)
super
end

def visit_call_operator_write_node(node)
colorize_call(node)
super
end

def visit_call_and_write_node(node)
colorize_call(node)
super
end

def visit_call_or_write_node(node)
colorize_call(node)
super
end

def visit_interpolated_symbol_node(node)
dispatch node.opening_loc, :symbol
node.parts.each do |part|
Expand Down Expand Up @@ -279,6 +305,21 @@ def visit_symbol_node(node)
dispatch node.closing_loc, :symbol
end
end

private

def colorize_call(node)
if @colorize_call
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(node.name)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colorize_call calls Regexp#match? with node.name. In Prism, CallNode#name is commonly a Symbol (e.g., :[]=, :+), and passing a Symbol to match? raises TypeError. Consider normalizing once (e.g., name_str = node.name.to_s and name_sym = node.name.is_a?(Symbol) ? node.name : node.name.to_sym) and using name_sym for OPERATORS.include? and name_str for the uppercase check.

Suggested change
if node.call_operator_loc.nil? && OPERATORS.include?(node.name)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(node.name)
name = node.name
name_sym = name.is_a?(Symbol) ? name : name.to_sym
name_str = name.to_s
if node.call_operator_loc.nil? && OPERATORS.include?(name_sym)
# Operators should not be colored as method call
elsif (node.call_operator_loc.nil? || node.call_operator_loc.slice == "::") &&
/\A\p{Upper}/.match?(name_str)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides that, CallOperatorWriteNode seems not to have the name method. I'll fix it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in db9cd4c.

Regexp#match? accepts symbols, so there's no need to call to_s.

# Constant-like methods should not be colored as method call
else
dispatch node.message_loc, :message_name
end
end
end
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/irb/color_printer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def text(str, width = nil)
when /\A#</, '=', '>'
super(@colorize ? Color.colorize(str, [:GREEN]) : str, width)
else
super(@colorize ? Color.colorize_code(str, ignore_error: true) : str, width)
super(@colorize ? Color.colorize_code(str, ignore_error: true, colorize_call: false) : str, width)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not want to colorize call in this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above code was introduced to fix the following failure of the debug compatibility test:

https://github.com/ruby/irb/actions/runs/23522320220/job/68468010147

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I don't like that we update IRB lib code to workaround a test.

I'd prefer:

  1. Ship without this workaround
  2. Propose test changes to debug
  3. Let CI fail for a few days until 2 is merged

An alternative could be to disable this test until 2 is merged.

@tompng WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's correct to highlight member names as method names in the Struct inspect output.

Furthermore, the results might differ in environments where variables with the same names as the members are defined.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, the results might differ in environments where variables with the same names as the members are defined.

This might be an overthought, as no local variables are specified here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ship without this workaround and fix debug test seems good 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reverted it in fa97fdd.

end
end
end
Expand Down
2 changes: 1 addition & 1 deletion test/irb/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def run_ruby_file(timeout: TIMEOUT_SEC, via_irb: false, &block)
lines << line

# means the breakpoint is triggered
if line.match?(/binding\.irb/)
if line.match?(/binding(?<color>\e\[\d+m)?\.\g<color>?irb/)
while command = @commands.shift
write.puts(command)
end
Expand Down
65 changes: 40 additions & 25 deletions test/irb/test_color.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ def test_colorize_code
"8i" => "#{BLUE}#{BOLD}8i#{CLEAR}",
"['foo', :bar]" => "[#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}bar#{CLEAR}]",
"class A; end" => "#{GREEN}class#{CLEAR} #{BLUE}#{BOLD}#{UNDERLINE}A#{CLEAR}; #{GREEN}end#{CLEAR}",
"def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; bar; #{GREEN}end#{CLEAR}",
'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.new(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})",
"def self.foo; bar; end" => "#{GREEN}def#{CLEAR} #{CYAN}#{BOLD}self#{CLEAR}.#{BLUE}#{BOLD}foo#{CLEAR}; #{BLUE}bar#{CLEAR}; #{GREEN}end#{CLEAR}",
'erb = ERB.new("a#{nil}b", trim_mode: "-")' => "erb = #{BLUE}#{BOLD}#{UNDERLINE}ERB#{CLEAR}.#{BLUE}new#{CLEAR}(#{RED}#{BOLD}\"#{CLEAR}#{RED}a#{CLEAR}#{RED}\#{#{CLEAR}#{CYAN}#{BOLD}nil#{CLEAR}#{RED}}#{CLEAR}#{RED}b#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}, #{MAGENTA}trim_mode:#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}-#{CLEAR}#{RED}#{BOLD}\"#{CLEAR})",
"# comment" => "#{BLUE}#{BOLD}# comment#{CLEAR}",
"def f;yield(hello);end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR};#{GREEN}yield#{CLEAR}(hello);#{GREEN}end#{CLEAR}",
"def f;yield(hello);end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR};#{GREEN}yield#{CLEAR}(#{BLUE}hello#{CLEAR});#{GREEN}end#{CLEAR}",
'"##@var]"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\##{CLEAR}#{RED}\##{CLEAR}@var#{RED}]#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}a#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}b#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}e#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}",
'"foo#{a} #{b}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}#{BLUE}a#{CLEAR}#{RED}}#{CLEAR}#{RED} #{CLEAR}#{RED}\#{#{CLEAR}#{BLUE}b#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'/r#{e}g/' => "#{RED}#{BOLD}/#{CLEAR}#{RED}r#{CLEAR}#{RED}\#{#{CLEAR}#{BLUE}e#{CLEAR}#{RED}}#{CLEAR}#{RED}g#{CLEAR}#{RED}#{BOLD}/#{CLEAR}",
"'a\nb'" => "#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}\n#{RED}b#{CLEAR}#{RED}#{BOLD}'#{CLEAR}",
"%[str]" => "#{RED}#{BOLD}%[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
"%Q[str]" => "#{RED}#{BOLD}%Q[#{CLEAR}#{RED}str#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
Expand All @@ -90,33 +90,48 @@ def test_colorize_code
"[:end, 2]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}end#{CLEAR}, #{BLUE}#{BOLD}2#{CLEAR}]",
"[:>, 3]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}>#{CLEAR}, #{BLUE}#{BOLD}3#{CLEAR}]",
"[:`, 4]" => "[#{YELLOW}:#{CLEAR}#{YELLOW}`#{CLEAR}, #{BLUE}#{BOLD}4#{CLEAR}]",
":Hello ? world : nil" => "#{YELLOW}:#{CLEAR}#{YELLOW}Hello#{CLEAR} ? world : #{CYAN}#{BOLD}nil#{CLEAR}",
'raise "foo#{bar}baz"' => "raise #{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}bar#{RED}}#{CLEAR}#{RED}baz#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'["#{obj.inspect}"]' => "[#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}obj.inspect#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}]",
'URI.parse "#{}"' => "#{BLUE}#{BOLD}#{UNDERLINE}URI#{CLEAR}.parse #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
":Hello ? world : nil" => "#{YELLOW}:#{CLEAR}#{YELLOW}Hello#{CLEAR} ? #{BLUE}world#{CLEAR} : #{CYAN}#{BOLD}nil#{CLEAR}",
'raise "foo#{bar}baz"' => "#{BLUE}raise#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}foo#{CLEAR}#{RED}\#{#{CLEAR}#{BLUE}bar#{CLEAR}#{RED}}#{CLEAR}#{RED}baz#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
'["#{obj.inspect}"]' => "[#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{BLUE}obj#{CLEAR}.#{BLUE}inspect#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}]",
'URI.parse "#{}"' => "#{BLUE}#{BOLD}#{UNDERLINE}URI#{CLEAR}.#{BLUE}parse#{CLEAR} #{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
"begin\nrescue\nend" => "#{GREEN}begin#{CLEAR}\n#{GREEN}rescue#{CLEAR}\n#{GREEN}end#{CLEAR}",
"foo %w[bar]" => "foo #{RED}#{BOLD}%w[#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
"foo %i[bar]" => "foo #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}",
"foo :@bar, baz, :@@qux, :$quux" => "foo #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, baz, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}",
"foo %w[bar]" => "#{BLUE}foo#{CLEAR} #{RED}#{BOLD}%w[#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
"foo %i[bar]" => "#{BLUE}foo#{CLEAR} #{YELLOW}%i[#{CLEAR}#{YELLOW}bar#{CLEAR}#{YELLOW}]#{CLEAR}",
"foo :@bar, baz, :@@qux, :$quux" => "#{BLUE}foo#{CLEAR} #{YELLOW}:#{CLEAR}#{YELLOW}@bar#{CLEAR}, #{BLUE}baz#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}@@qux#{CLEAR}, #{YELLOW}:#{CLEAR}#{YELLOW}$quux#{CLEAR}",
"`echo`" => "#{RED}#{BOLD}`#{CLEAR}#{RED}echo#{CLEAR}#{RED}#{BOLD}`#{CLEAR}",
"\t" => Reline::Unicode.escape_for_print("\t") == ' ' ? ' ' : "\t", # not ^I
"foo(*%W(bar))" => "foo(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
"foo(*%W(bar))" => "#{BLUE}foo#{CLEAR}(*#{RED}#{BOLD}%W(#{CLEAR}#{RED}bar#{CLEAR}#{RED}#{BOLD})#{CLEAR})",
"$stdout" => "#{GREEN}#{BOLD}$stdout#{CLEAR}",
"$&" => "#{GREEN}#{BOLD}$&#{CLEAR}",
"$1" => "#{GREEN}#{BOLD}$1#{CLEAR}",
"__END__" => "#{GREEN}__END__#{CLEAR}",
"foo\n__END__\nbar" => "foo\n#{GREEN}__END__#{CLEAR}\nbar",
"foo\n<<A\0\0bar\nA\nbaz" => "foo\n#{RED}<<A#{CLEAR}^@^@bar\n#{RED}A#{CLEAR}\nbaz",
"foo\n__END__\nbar" => "#{BLUE}foo#{CLEAR}\n#{GREEN}__END__#{CLEAR}\nbar",
"foo\n<<A\0\0bar\nA\nbaz" => "#{BLUE}foo#{CLEAR}\n#{RED}<<A#{CLEAR}^@^@bar\n#{RED}A#{CLEAR}\nbaz",
"<<A+1\nA" => "#{RED}<<A#{CLEAR}+#{BLUE}#{BOLD}1#{CLEAR}\n#{RED}A#{CLEAR}",
"4.5.6" => "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}",
"\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}m\n",
"\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{BLUE}m#{CLEAR}\n",
"<<EOS\nhere\nEOS" => "#{RED}<<EOS#{CLEAR}\n#{RED}here#{CLEAR}\n#{RED}EOS#{CLEAR}",
"[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}^S#{CLEAR}",
"def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}#{RED}#{REVERSE})#{CLEAR} #{GREEN}end#{CLEAR}",
"nil = 1" => "#{CYAN}#{BOLD}nil#{CLEAR} #{RED}#{REVERSE}=#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}",
"alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} #{RED}#{REVERSE}$1#{CLEAR}",
"class bad; end" => "#{GREEN}class#{CLEAR} #{RED}#{REVERSE}bad#{CLEAR}; #{GREEN}end#{CLEAR}",
"def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}@a#{CLEAR}) #{GREEN}end#{CLEAR}",
"a.foo" => "#{BLUE}a#{CLEAR}.#{BLUE}foo#{CLEAR}",
"a.foo = 1" => "#{BLUE}a#{CLEAR}.#{BLUE}foo#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}",
"a.foo += 1" => "#{BLUE}a#{CLEAR}.#{BLUE}foo#{CLEAR} += #{BLUE}#{BOLD}1#{CLEAR}",
"a.foo &&= 1" => "#{BLUE}a#{CLEAR}.#{BLUE}foo#{CLEAR} &&= #{BLUE}#{BOLD}1#{CLEAR}",
"a.foo ||= 1" => "#{BLUE}a#{CLEAR}.#{BLUE}foo#{CLEAR} ||= #{BLUE}#{BOLD}1#{CLEAR}",
"a[:foo]" => "#{BLUE}a#{CLEAR}[#{YELLOW}:#{CLEAR}#{YELLOW}foo#{CLEAR}]",
"a[:foo] = 1" => "#{BLUE}a#{CLEAR}[#{YELLOW}:#{CLEAR}#{YELLOW}foo#{CLEAR}] = #{BLUE}#{BOLD}1#{CLEAR}",
"a+1" => "#{BLUE}a#{CLEAR}+#{BLUE}#{BOLD}1#{CLEAR}",
"a.+(1)" => "#{BLUE}a#{CLEAR}.#{BLUE}+#{CLEAR}(#{BLUE}#{BOLD}1#{CLEAR})",
"-a" => "-#{BLUE}a#{CLEAR}",
"a.-@" => "#{BLUE}a#{CLEAR}.#{BLUE}-@#{CLEAR}",
'Foo(1)' => "#{BLUE}#{BOLD}#{UNDERLINE}Foo#{CLEAR}(#{BLUE}#{BOLD}1#{CLEAR})",
'Foo::Bar(1)' => "#{BLUE}#{BOLD}#{UNDERLINE}Foo#{CLEAR}::#{BLUE}#{BOLD}#{UNDERLINE}Bar#{CLEAR}(#{BLUE}#{BOLD}1#{CLEAR})",
'Foo.Bar(1)' => "#{BLUE}#{BOLD}#{UNDERLINE}Foo#{CLEAR}.#{BLUE}Bar#{CLEAR}(#{BLUE}#{BOLD}1#{CLEAR})",
'Foo&.Bar(1)' => "#{BLUE}#{BOLD}#{UNDERLINE}Foo#{CLEAR}&.#{BLUE}Bar#{CLEAR}(#{BLUE}#{BOLD}1#{CLEAR})",
}

tests.each do |code, result|
Expand All @@ -138,9 +153,9 @@ def test_colorize_code

def test_colorize_code_with_local_variables
code = "a /(b +1)/i"
result_without_lvars = "a #{RED}#{BOLD}/#{CLEAR}#{RED}(b +1)#{CLEAR}#{RED}#{BOLD}/i#{CLEAR}"
result_with_lvar = "a /(b #{BLUE}#{BOLD}+1#{CLEAR})/i"
result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i"
result_without_lvars = "#{BLUE}a#{CLEAR} #{RED}#{BOLD}/#{CLEAR}#{RED}(b +1)#{CLEAR}#{RED}#{BOLD}/i#{CLEAR}"
result_with_lvar = "a /(#{BLUE}b#{CLEAR} #{BLUE}#{BOLD}+1#{CLEAR})/#{BLUE}i#{CLEAR}"
result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/#{BLUE}i#{CLEAR}"

assert_equal_with_term(result_without_lvars, code)
assert_equal_with_term(result_with_lvar, code, local_variables: [:a])
Expand Down Expand Up @@ -169,7 +184,7 @@ def test_colorize_code_complete_false
"'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}bar#{CLEAR}",
"('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}",
"if true" => "#{GREEN}if#{CLEAR} #{CYAN}#{BOLD}true#{CLEAR}",
"tap do end end tap do" => "tap #{GREEN}do#{CLEAR} #{GREEN}end#{CLEAR} #{RED}#{REVERSE}end#{CLEAR} tap #{GREEN}do#{CLEAR}",
"tap do end end tap do" => "#{BLUE}tap#{CLEAR} #{GREEN}do#{CLEAR} #{GREEN}end#{CLEAR} #{RED}#{REVERSE}end#{CLEAR} #{BLUE}tap#{CLEAR} #{GREEN}do#{CLEAR}",

# Specially handled cases
"class" => "#{GREEN}class#{CLEAR}",
Expand All @@ -178,16 +193,16 @@ def test_colorize_code_complete_false
"module" => "#{GREEN}module#{CLEAR}",
"module#" => "#{GREEN}module#{CLEAR}#{BLUE}#{BOLD}\##{CLEAR}",
"module;" => "#{RED}#{REVERSE}module#{CLEAR};",
"class owner_module" => "#{GREEN}class#{CLEAR} owner_module",
"module owner_module" => "#{GREEN}module#{CLEAR} owner_module",
"class owner_module" => "#{GREEN}class#{CLEAR} #{BLUE}owner_module#{CLEAR}",
"module owner_module" => "#{GREEN}module#{CLEAR} #{BLUE}owner_module#{CLEAR}",
"a, b" => "a, b",
"a, b#" => "a, b#{BLUE}#{BOLD}\##{CLEAR}",
"a, b;" => "#{RED}#{REVERSE}a, b#{CLEAR};",
"def f(a,#" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR}(a,#{BLUE}#{BOLD}\##{CLEAR}",
"[*" => "[*",
"f(*" => "f(*",
"f(**" => "f(**",
"f(&" => "f(&",
"f(*" => "#{BLUE}f#{CLEAR}(*",
"f(**" => "#{BLUE}f#{CLEAR}(**",
"f(&" => "#{BLUE}f#{CLEAR}(&",
"def f =" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}f#{CLEAR} =",
"=begin" => "#{BLUE}#{BOLD}=begin#{CLEAR}",
}.each do |code, result|
Expand Down
Loading