diff --git a/irb.gemspec b/irb.gemspec
index b29002f59..a024210e7 100644
--- a/irb.gemspec
+++ b/irb.gemspec
@@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
+ spec.add_dependency "prism", ">= 1.0"
spec.add_dependency "reline", ">= 0.4.2"
spec.add_dependency "rdoc", ">= 4.0.0"
end
diff --git a/lib/irb/color.rb b/lib/irb/color.rb
index fca942b28..872e6b6af 100644
--- a/lib/irb/color.rb
+++ b/lib/irb/color.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'reline'
-require 'ripper'
+require 'prism'
require_relative 'ruby-lex'
module IRB # :nodoc:
@@ -18,64 +18,96 @@ module Color
CYAN = 36
WHITE = 37
- TOKEN_KEYWORDS = {
- on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'],
- on_const: ['ENV'],
- }
- private_constant :TOKEN_KEYWORDS
-
- # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq
- ALL = -1
- private_constant :ALL
-
- begin
- # Following pry's colors where possible, but sometimes having a compromise like making
- # backtick and regexp as red (string's color, because they're sharing tokens).
- TOKEN_SEQ_EXPRS = {
- on_CHAR: [[BLUE, BOLD], ALL],
- on_backtick: [[RED, BOLD], ALL],
- on_comment: [[BLUE, BOLD], ALL],
- on_const: [[BLUE, BOLD, UNDERLINE], ALL],
- on_embexpr_beg: [[RED], ALL],
- on_embexpr_end: [[RED], ALL],
- on_embvar: [[RED], ALL],
- on_float: [[MAGENTA, BOLD], ALL],
- on_gvar: [[GREEN, BOLD], ALL],
- on_heredoc_beg: [[RED], ALL],
- on_heredoc_end: [[RED], ALL],
- on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN],
- on_imaginary: [[BLUE, BOLD], ALL],
- on_int: [[BLUE, BOLD], ALL],
- on_kw: [[GREEN], ALL],
- on_label: [[MAGENTA], ALL],
- on_label_end: [[RED, BOLD], ALL],
- on_qsymbols_beg: [[RED, BOLD], ALL],
- on_qwords_beg: [[RED, BOLD], ALL],
- on_rational: [[BLUE, BOLD], ALL],
- on_regexp_beg: [[RED, BOLD], ALL],
- on_regexp_end: [[RED, BOLD], ALL],
- on_symbeg: [[YELLOW], ALL],
- on_symbols_beg: [[RED, BOLD], ALL],
- on_tstring_beg: [[RED, BOLD], ALL],
- on_tstring_content: [[RED], ALL],
- on_tstring_end: [[RED, BOLD], ALL],
- on_words_beg: [[RED, BOLD], ALL],
- on_parse_error: [[RED, REVERSE], ALL],
- compile_error: [[RED, REVERSE], ALL],
- on_assign_error: [[RED, REVERSE], ALL],
- on_alias_error: [[RED, REVERSE], ALL],
- on_class_name_error:[[RED, REVERSE], ALL],
- on_param_error: [[RED, REVERSE], ALL],
- on___end__: [[GREEN], ALL],
- }
- rescue NameError
- # Give up highlighting Ripper-incompatible older Ruby
- TOKEN_SEQ_EXPRS = {}
+ # Following pry's colors where possible
+ TOKEN_SEQS = {
+ KEYWORD_NIL: [CYAN, BOLD],
+ KEYWORD_SELF: [CYAN, BOLD],
+ KEYWORD_TRUE: [CYAN, BOLD],
+ KEYWORD_FALSE: [CYAN, BOLD],
+ KEYWORD___FILE__: [CYAN, BOLD],
+ KEYWORD___LINE__: [CYAN, BOLD],
+ KEYWORD___ENCODING__: [CYAN, BOLD],
+ CHARACTER_LITERAL: [BLUE, BOLD],
+ BACKTICK: [RED, BOLD],
+ COMMENT: [BLUE, BOLD],
+ EMBDOC_BEGIN: [BLUE, BOLD],
+ EMBDOC_LINE: [BLUE, BOLD],
+ EMBDOC_END: [BLUE, BOLD],
+ CONSTANT: [BLUE, BOLD, UNDERLINE],
+ EMBEXPR_BEGIN: [RED],
+ EMBEXPR_END: [RED],
+ EMBVAR: [RED],
+ FLOAT: [MAGENTA, BOLD],
+ GLOBAL_VARIABLE: [GREEN, BOLD],
+ HEREDOC_START: [RED],
+ HEREDOC_END: [RED],
+ FLOAT_IMAGINARY: [BLUE, BOLD],
+ INTEGER_IMAGINARY: [BLUE, BOLD],
+ FLOAT_RATIONAL_IMAGINARY: [BLUE, BOLD],
+ INTEGER_RATIONAL_IMAGINARY: [BLUE, BOLD],
+ INTEGER: [BLUE, BOLD],
+ INTEGER_RATIONAL: [BLUE, BOLD],
+ FLOAT_RATIONAL: [BLUE, BOLD],
+ KEYWORD_END: [GREEN],
+ KEYWORD_CLASS: [GREEN],
+ KEYWORD_MODULE: [GREEN],
+ KEYWORD_IF: [GREEN],
+ KEYWORD_IF_MODIFIER: [GREEN],
+ KEYWORD_UNLESS_MODIFIER: [GREEN],
+ KEYWORD_WHILE_MODIFIER: [GREEN],
+ KEYWORD_UNTIL_MODIFIER: [GREEN],
+ KEYWORD_THEN: [GREEN],
+ KEYWORD_UNLESS: [GREEN],
+ KEYWORD_ELSE: [GREEN],
+ KEYWORD_WHILE: [GREEN],
+ KEYWORD_UNTIL: [GREEN],
+ KEYWORD_CASE: [GREEN],
+ KEYWORD_WHEN: [GREEN],
+ KEYWORD_IN: [GREEN],
+ KEYWORD_DEF: [GREEN],
+ KEYWORD_DO: [GREEN],
+ KEYWORD_DO_LOOP: [GREEN],
+ KEYWORD_FOR: [GREEN],
+ KEYWORD_BEGIN: [GREEN],
+ KEYWORD_RESCUE: [GREEN],
+ KEYWORD_ENSURE: [GREEN],
+ KEYWORD_ALIAS: [GREEN],
+ KEYWORD_UNDEF: [GREEN],
+ KEYWORD_BEGIN_UPCASE: [GREEN],
+ KEYWORD_END_UPCASE: [GREEN],
+ KEYWORD_YIELD: [GREEN],
+ KEYWORD_REDO: [GREEN],
+ KEYWORD_RETRY: [GREEN],
+ KEYWORD_NEXT: [GREEN],
+ KEYWORD_BREAK: [GREEN],
+ KEYWORD_SUPER: [GREEN],
+ KEYWORD_RETURN: [GREEN],
+ KEYWORD_DEFINED: [GREEN],
+ KEYWORD_NOT: [GREEN],
+ KEYWORD_AND: [GREEN],
+ KEYWORD_OR: [GREEN],
+ LABEL: [MAGENTA],
+ LABEL_END: [RED, BOLD],
+ PERCENT_UPPER_W: [RED, BOLD],
+ PERCENT_LOWER_W: [RED, BOLD],
+ PERCENT_LOWER_X: [RED, BOLD],
+ REGEXP_BEGIN: [RED, BOLD],
+ REGEXP_END: [RED, BOLD],
+ STRING_BEGIN: [RED, BOLD],
+ STRING_CONTENT: [RED],
+ STRING_END: [RED, BOLD],
+ __END__: [GREEN],
+ # tokens from syntax tree traversal
+ method_name: [BLUE, BOLD],
+ symbol: [YELLOW],
+ # special colorization
+ error: [RED, REVERSE],
+ const_env: [CYAN, BOLD],
+ }.transform_values do |styles|
+ styles.map { |style| "\e[#{style}m" }.join
end
- private_constant :TOKEN_SEQ_EXPRS
-
- ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') }
- private_constant :ERROR_TOKENS
+ CLEAR_SEQ = "\e[#{CLEAR}m"
+ private_constant :TOKEN_SEQS, :CLEAR_SEQ
class << self
def colorable?
@@ -112,14 +144,13 @@ def inspect_colorable?(obj, seen: {}.compare_by_identity)
end
def clear(colorable: colorable?)
- return '' unless colorable
- "\e[#{CLEAR}m"
+ colorable ? CLEAR_SEQ : ''
end
def colorize(text, seq, colorable: colorable?)
return text unless colorable
seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('')
- "#{seq}#{text}#{clear(colorable: colorable)}"
+ "#{seq}#{text}#{CLEAR_SEQ}"
end
# If `complete` is false (code is incomplete), this does not warn compile_error.
@@ -128,135 +159,129 @@ def colorize(text, seq, colorable: colorable?)
def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: [])
return code unless colorable
- symbol_state = SymbolState.new
- colored = +''
- lvars_code = RubyLex.generate_local_variables_assign_code(local_variables)
- code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code
+ result = Prism.parse_lex(code, scopes: [local_variables])
- scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr|
- # handle uncolorable code
- if token.nil?
- colored << Reline::Unicode.escape_for_print(str)
- next
- end
+ # IRB::ColorPrinter skips colorizing syntax invalid fragments
+ return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success?
+
+ errors = result.errors
+ unless complete
+ errors = errors.reject { |error| error.message =~ /\Aexpected a|unexpected end-of-input|unterminated/ }
+ end
+
+ prism_node, prism_tokens = result.value
+ visitor = ColorizeVisitor.new
+ 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] }
+ tokens = prism_tokens.map { |t,| [t.location.start_line, t.location.start_column, 2, t.location.end_line, t.location.end_column, t.type, t.value] }
+ tokens.pop if tokens.last&.[](5) == :EOF
- # IRB::ColorPrinter skips colorizing fragments with any invalid token
- if ignore_error && ERROR_TOKENS.include?(token)
- return Reline::Unicode.escape_for_print(code)
+ colored = +''
+ line_index = 0
+ col = 0
+ lines = code.lines
+ flush = -> next_line_index, next_col {
+ return if next_line_index == line_index && next_col == col
+ (line_index...[next_line_index, lines.size].min).each do |ln|
+ colored << Reline::Unicode.escape_for_print(lines[line_index].byteslice(col..))
+ line_index = ln + 1
+ col = 0
+ end
+ unless col == next_col
+ colored << Reline::Unicode.escape_for_print(lines[next_line_index].byteslice(col..next_col - 1))
end
+ }
+
+ (visitor.tokens + tokens + error_tokens).sort.each do |start_line, start_column, _priority, end_line, end_column, type, value|
+ next if start_line - 1 < line_index || (start_line - 1 == line_index && start_column < col)
- in_symbol = symbol_state.scan_token(token)
- str.each_line do |line|
- line = Reline::Unicode.escape_for_print(line)
- if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol)
- colored << seq.map { |s| "\e[#{s}m" }.join('')
- colored << line.sub(/\Z/, clear(colorable: colorable))
- else
- colored << line
+ flush.call(start_line - 1, start_column)
+ if type == :CONSTANT && value == 'ENV'
+ color = TOKEN_SEQS[:const_env]
+ elsif type == :__END__
+ color = TOKEN_SEQS[type]
+ end_line = start_line
+ value = '__END__'
+ end_column = start_column + 7
+ else
+ color = TOKEN_SEQS[type]
+ end
+ if color
+ value.split(/(\n)/).each do |s|
+ colored << (s == "\n" ? s : "#{color}#{Reline::Unicode.escape_for_print(s)}#{CLEAR_SEQ}")
end
+ else
+ colored << value
end
+ line_index = end_line - 1
+ col = end_column
end
-
- if lvars_code
- raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n")
- colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors
- end
+ flush.call lines.size, 0
colored
end
- private
-
- def without_circular_ref(obj, seen:, &block)
- return false if seen.key?(obj)
- seen[obj] = true
- block.call
- ensure
- seen.delete(obj)
- end
+ class ColorizeVisitor < Prism::Visitor
+ attr_reader :tokens
+ def initialize
+ @tokens = []
+ end
- def scan(code, allow_last_error:)
- verbose, $VERBOSE = $VERBOSE, nil
- RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no|
- lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no)
- byte_pos = 0
- line_positions = [0]
- inner_code.lines.each do |line|
- line_positions << line_positions.last + line.bytesize
+ def dispatch(location, type)
+ if location
+ @tokens << [location.start_line, location.start_column, 1, location.end_line, location.end_column, type, location.slice]
end
+ end
- on_scan = proc do |elem|
- start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1]
+ def visit_array_node(node)
+ if node.opening&.match?(/\A%[iI]/)
+ dispatch node.opening_loc, :symbol
+ dispatch node.closing_loc, :symbol
+ end
+ super
+ end
- # yield uncolorable code
- if byte_pos < start_pos
- yield(nil, inner_code.byteslice(byte_pos...start_pos), nil)
- end
+ def visit_def_node(node)
+ dispatch node.name_loc, :method_name
+ super
+ end
- if byte_pos <= start_pos
- str = elem.tok
- yield(elem.event, str, elem.state)
- byte_pos = start_pos + str.bytesize
+ def visit_interpolated_symbol_node(node)
+ dispatch node.opening_loc, :symbol
+ node.parts.each do |part|
+ case part
+ when Prism::StringNode
+ dispatch part.content_loc, :symbol
+ when Prism::EmbeddedStatementsNode
+ dispatch part.opening_loc, :symbol
+ dispatch part.closing_loc, :symbol
end
end
+ dispatch node.closing_loc, :symbol
+ super
+ end
- lexer.scan.each do |elem|
- next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
- on_scan.call(elem)
+ def visit_symbol_node(node)
+ if (node.opening_loc.nil? && node.closing == ':') || node.closing&.match?(/\A['"]:\z/)
+ # Colorize { symbol: 1 } and { 'symbol': 1 } as label
+ dispatch node.location, :LABEL
+ else
+ dispatch node.opening_loc, :symbol
+ dispatch node.value_loc, :symbol
+ dispatch node.closing_loc, :symbol
end
- # yield uncolorable DATA section
- yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize
end
- ensure
- $VERBOSE = verbose
end
- def dispatch_seq(token, expr, str, in_symbol:)
- if ERROR_TOKENS.include?(token)
- TOKEN_SEQ_EXPRS[token][0]
- elsif in_symbol
- [YELLOW]
- elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
- [CYAN, BOLD]
- elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0)
- seq
- else
- nil
- end
- end
- end
-
- # A class to manage a state to know whether the current token is for Symbol or not.
- class SymbolState
- def initialize
- # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol.
- @stack = []
- end
+ private
- # Return true if the token is a part of Symbol.
- def scan_token(token)
- prev_state = @stack.last
- case token
- when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg
- @stack << true
- when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick
- if @stack.last # Pop only when it's Symbol
- @stack.pop
- return prev_state
- end
- when :on_tstring_beg
- @stack << false
- when :on_embexpr_beg
- @stack << false
- return prev_state
- when :on_tstring_end # :on_tstring_end may close Symbol
- @stack.pop
- return prev_state
- when :on_embexpr_end
- @stack.pop
- end
- @stack.last
+ def without_circular_ref(obj, seen:, &block)
+ return false if seen.key?(obj)
+ seen[obj] = true
+ block.call
+ ensure
+ seen.delete(obj)
end
end
- private_constant :SymbolState
end
end
diff --git a/lib/irb/context.rb b/lib/irb/context.rb
index 505bed80a..e1d363ec4 100644
--- a/lib/irb/context.rb
+++ b/lib/irb/context.rb
@@ -660,7 +660,7 @@ def colorize_input(input, complete:)
lvars = local_variables || []
if parse_command(input)
name, sep, arg = input.split(/(\s+)/, 2)
- arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars)
+ arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) if arg
"#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}"
else
IRB::Color.colorize_code(input, complete: complete, local_variables: lvars)
diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb
index 9d78f5233..0b7fa83a7 100644
--- a/test/irb/test_color.rb
+++ b/test/irb/test_color.rb
@@ -77,9 +77,9 @@ def test_colorize_code
"%w[a b]" => "#{RED}#{BOLD}%w[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
"%W[a b]" => "#{RED}#{BOLD}%W[#{CLEAR}#{RED}a#{CLEAR} #{RED}b#{CLEAR}#{RED}#{BOLD}]#{CLEAR}",
"%s[a b]" => "#{YELLOW}%s[#{CLEAR}#{YELLOW}a b#{CLEAR}#{YELLOW}]#{CLEAR}",
- "%i[c d]" => "#{YELLOW}%i[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
- "%I[c d]" => "#{YELLOW}%I[#{CLEAR}#{YELLOW}c#{CLEAR}#{YELLOW} #{CLEAR}#{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
- "{'a': 1}" => "{#{RED}#{BOLD}'#{CLEAR}#{RED}a#{CLEAR}#{RED}#{BOLD}':#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}}",
+ "%i[c d]" => "#{YELLOW}%i[#{CLEAR}#{YELLOW}c#{CLEAR} #{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
+ "%I[c d]" => "#{YELLOW}%I[#{CLEAR}#{YELLOW}c#{CLEAR} #{YELLOW}d#{CLEAR}#{YELLOW}]#{CLEAR}",
+ "{'a': 1}" => "{#{MAGENTA}'a':#{CLEAR} #{BLUE}#{BOLD}1#{CLEAR}}",
":Struct" => "#{YELLOW}:#{CLEAR}#{YELLOW}Struct#{CLEAR}",
'"#{}"' => "#{RED}#{BOLD}\"#{CLEAR}#{RED}\#{#{CLEAR}#{RED}}#{CLEAR}#{RED}#{BOLD}\"#{CLEAR}",
':"a#{}b"' => "#{YELLOW}:\"#{CLEAR}#{YELLOW}a#{CLEAR}#{YELLOW}\#{#{CLEAR}#{YELLOW}}#{CLEAR}#{YELLOW}b#{CLEAR}#{YELLOW}\"#{CLEAR}",
@@ -106,41 +106,16 @@ def test_colorize_code
"foo\n__END__\nbar" => "foo\n#{GREEN}__END__#{CLEAR}\nbar",
"foo\n< "foo\n#{RED}< "#{RED}< "#{MAGENTA}#{BOLD}4.5#{CLEAR}#{RED}#{REVERSE}.6#{CLEAR}",
- "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}#{RED}#{REVERSE}m#{CLEAR}\n",
+ "\e[0m\n" => "#{RED}#{REVERSE}^[#{CLEAR}[#{BLUE}#{BOLD}0#{CLEAR}m\n",
"< "#{RED}<= Gem::Version.new('3.0.0')
- tests.merge!({
- "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}]#{CLEAR}#{RED}#{REVERSE}^S#{CLEAR}",
- })
- tests.merge!({
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) #{RED}#{REVERSE}end#{CLEAR}",
- "nil = 1" => "#{RED}#{REVERSE}nil#{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}",
- })
- if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2.0')
- tests.merge!({
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}#{RED}#{REVERSE})#{CLEAR} #{RED}#{REVERSE}end#{CLEAR}",
- })
- end
- else
- tests.merge!({
- "[1]]]\u0013" => "[#{BLUE}#{BOLD}1#{CLEAR}]#{RED}#{REVERSE}]#{CLEAR}]^S",
- "def req(true) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(#{RED}#{REVERSE}true#{CLEAR}) end",
- "nil = 1" => "#{CYAN}#{BOLD}nil#{CLEAR} = #{BLUE}#{BOLD}1#{CLEAR}",
- "alias $x $1" => "#{GREEN}alias#{CLEAR} #{GREEN}#{BOLD}$x#{CLEAR} $1",
- "class bad; end" => "#{GREEN}class#{CLEAR} bad; #{GREEN}end#{CLEAR}",
- "def req(@a) end" => "#{GREEN}def#{CLEAR} #{BLUE}#{BOLD}req#{CLEAR}(@a) #{GREEN}end#{CLEAR}",
- })
- end
+ "[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}",
+ }
tests.each do |code, result|
assert_equal_with_term(result, code, complete: true)
@@ -166,15 +141,15 @@ def test_colorize_code_with_local_variables
result_with_lvars = "a /(b +#{BLUE}#{BOLD}1#{CLEAR})/i"
assert_equal_with_term(result_without_lvars, code)
- assert_equal_with_term(result_with_lvar, code, local_variables: ['a'])
- assert_equal_with_term(result_with_lvars, code, local_variables: ['a', 'b'])
+ assert_equal_with_term(result_with_lvar, code, local_variables: [:a])
+ assert_equal_with_term(result_with_lvars, code, local_variables: [:a, :b])
end
def test_colorize_code_complete_true
# `complete: true` behaviors. Warn end-of-file.
{
- "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}",
- "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}foo#{CLEAR}",
+ "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{REVERSE}'#{CLEAR}#{RED}bar#{CLEAR}",
+ "('foo" => "(#{RED}#{REVERSE}'#{CLEAR}#{RED}foo#{CLEAR}",
}.each do |code, result|
assert_equal_with_term(result, code, complete: true)