Viewing File: /opt/cpanel/ea-ruby27/root/usr/local/share/gems/gems/irb-1.18.0/lib/irb/color.rb
# frozen_string_literal: true
require 'reline'
require 'prism'
require_relative 'ruby-lex'
module IRB # :nodoc:
module Color
CLEAR = 0
BOLD = 1
UNDERLINE = 4
REVERSE = 7
BLACK = 30
RED = 31
GREEN = 32
YELLOW = 33
BLUE = 34
MAGENTA = 35
CYAN = 36
WHITE = 37
# 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],
BACK_REFERENCE: [GREEN, 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_RESCUE_MODIFIER: [GREEN],
KEYWORD_THEN: [GREEN],
KEYWORD_UNLESS: [GREEN],
KEYWORD_ELSE: [GREEN],
KEYWORD_ELSIF: [GREEN],
KEYWORD_WHILE: [GREEN],
KEYWORD_UNTIL: [GREEN],
KEYWORD_CASE: [GREEN],
KEYWORD_WHEN: [GREEN],
KEYWORD_IN: [GREEN],
KEYWORD_DEF: [GREEN],
KEYWORD_DO: [GREEN],
KEYWORD_DO_BLOCK: [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],
NUMBERED_REFERENCE: [GREEN, 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: [CYAN, BOLD],
message_name: [CYAN],
symbol: [YELLOW],
# special colorization
error: [RED, REVERSE],
}.transform_values do |styles|
styles.map { |style| "\e[#{style}m" }.join
end
CLEAR_SEQ = "\e[#{CLEAR}m"
OPERATORS = %i(!= !~ =~ == === <=> > >= < <= & | ^ >> << - + % / * ** -@ +@ ~ ! [] []=)
private_constant :TOKEN_SEQS, :CLEAR_SEQ, :OPERATORS
class << self
def colorable?
supported = $stdout.tty? && (/mswin|mingw/.match?(RUBY_PLATFORM) || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
# because ruby/debug also uses irb's color module selectively,
# irb won't be activated in that case.
if IRB.respond_to?(:conf)
supported && !!IRB.conf.fetch(:USE_COLORIZE, true)
else
supported
end
end
def inspect_colorable?(obj, seen: {}.compare_by_identity)
case obj
when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass
true
when Hash
without_circular_ref(obj, seen: seen) do
obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) }
end
when Array
without_circular_ref(obj, seen: seen) do
obj.all? { |o| inspect_colorable?(o, seen: seen) }
end
when Range
inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen)
when Module
!obj.name.nil?
else
false
end
end
def clear(colorable: colorable?)
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_SEQ}"
end
# 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: [])
return code unless colorable
result = Prism.parse_lex(code, scopes: [local_variables])
# IRB::ColorPrinter skips colorizing syntax invalid fragments
return Reline::Unicode.escape_for_print(code) if ignore_error && !result.success?
prism_node, prism_tokens = result.value
errors = result.errors
unless complete
errors = filter_incomplete_code_errors(errors, prism_tokens)
end
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] }
error_tokens.reject! { |t| t.last.match?(/\A\s*\z/) }
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
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)
flush.call(start_line - 1, start_column)
if 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
flush.call lines.size, 0
colored
end
class ColorizeVisitor < Prism::Visitor
attr_reader :tokens
def initialize
@tokens = []
end
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
def visit_array_node(node)
if node.opening&.match?(/\A%[iI]/)
dispatch node.opening_loc, :symbol
dispatch node.closing_loc, :symbol
end
super
end
def visit_def_node(node)
dispatch node.name_loc, :method_name
super
end
def visit_alias_method_node(node)
dispatch_alias_method_name node.new_name
dispatch_alias_method_name node.old_name
super
end
def visit_call_node(node)
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)
# Constant-like methods should not be colored as method call
else
dispatch node.message_loc, :message_name
end
super
end
def visit_call_operator_write_node(node)
dispatch node.message_loc, :message_name
super
end
alias visit_call_and_write_node visit_call_operator_write_node
alias visit_call_or_write_node visit_call_operator_write_node
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
when Prism::EmbeddedVariableNode
dispatch part.operator_loc, :symbol
end
end
dispatch node.closing_loc, :symbol
super
end
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
end
private
def dispatch_alias_method_name(node)
if node.type == :symbol_node && node.opening_loc.nil?
dispatch node.value_loc, :method_name
end
end
end
private
FILTERED_ERROR_TYPES = [
:class_name, :module_name, # `class`, `class owner_module`
:write_target_unexpected, # `a, b`
:parameter_wild_loose_comma, # `def f(a,`
:argument_no_forwarding_star, # `[*`
:argument_no_forwarding_star_star, # `f(**`
:argument_no_forwarding_ampersand, # `f(&`
:def_endless, # `def f =`
:embdoc_term, # `=begin`
]
# Filter out syntax errors that are likely to be caused by incomplete code, to avoid showing misleading error highlights to users.
def filter_incomplete_code_errors(errors, tokens)
last_non_comment_space_token, = tokens.reverse_each.find do |t,|
t.type != :COMMENT && t.type != :EOF && t.type != :IGNORED_NEWLINE && t.type != :NEWLINE
end
last_offset = last_non_comment_space_token ? last_non_comment_space_token.location.end_offset : 0
errors.reject do |error|
error.message.match?(/\Aexpected a|unexpected end-of-input|unterminated/) ||
(error.location.end_offset == last_offset && FILTERED_ERROR_TYPES.include?(error.type))
end
end
def without_circular_ref(obj, seen:, &block)
return false if seen.key?(obj)
seen[obj] = true
block.call
ensure
seen.delete(obj)
end
end
end
end
Back to Directory