# module IRB::Color

### Constants

ALL

  A constant of all-bit 1 to match any Ripper’s state in dispatch_seq

BLUE
BOLD
CLEAR
CYAN
ERROR_TOKENS
GREEN
MAGENTA
RED
REVERSE
TOKEN_KEYWORDS
TOKEN_SEQ_EXPRS

  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).

UNDERLINE
YELLOW

### Public Class Methods

clear(colorable: colorable?) click to toggle source

    # File lib/irb/color.rb, line 104
    def clear(colorable: colorable?)
      return '' unless colorable
      "\e[#{CLEAR}m"
    end

colorable?() click to toggle source

    # File lib/irb/color.rb, line 79
    def colorable?
      $stdout.tty? && (/mswin|mingw/ =~ RUBY_PLATFORM || (ENV.key?('TERM') && ENV['TERM'] != 'dumb'))
    end

colorize(text, seq, colorable: colorable?) click to toggle source

    # File lib/irb/color.rb, line 109
    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)}"
    end

colorize_code(code, complete: true, ignore_error: false, colorable: colorable?) click to toggle source

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.

    # File lib/irb/color.rb, line 118
    def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?)
      return code unless colorable

      symbol_state = SymbolState.new
      colored = +''
      length = 0
      end_seen = false

      scan(code, allow_last_error: !complete) do |token, str, expr|
        # IRB::ColorPrinter skips colorizing fragments with any invalid token
        if ignore_error && ERROR_TOKENS.include?(token)
          return Reline::Unicode.escape_for_print(code)
        end

        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
          end
        end
        length += str.bytesize
        end_seen = true if token == :on___end__
      end

      # give up colorizing incomplete Ripper tokens
      unless end_seen or length == code.bytesize
        return Reline::Unicode.escape_for_print(code)
      end

      colored
    end

inspect_colorable?(obj, seen: {}.compare_by_identity) click to toggle source

    # File lib/irb/color.rb, line 83
    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

### Private Class Methods

dispatch_seq(token, expr, str, in_symbol:) click to toggle source

    # File lib/irb/color.rb, line 197
    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

scan(code, allow_last_error:) { |event, str, state| ... } click to toggle source

    # File lib/irb/color.rb, line 164
    def scan(code, allow_last_error:)
      pos = [1, 0]

      verbose, $VERBOSE = $VERBOSE, nil
      RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no|
        lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no)
        if lexer.respond_to?(:scan) # Ruby 2.7+
          lexer.scan.each do |elem|
            str = elem.tok
            next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message
            next if ([elem.pos[0], elem.pos[1] + str.bytesize] <=> pos) <= 0

            str.each_line do |line|
              if line.end_with?("\n")
                pos[0] += 1
                pos[1] = 0
              else
                pos[1] += line.bytesize
              end
            end

            yield(elem.event, str, elem.state)
          end
        else
          lexer.parse.each do |elem|
            yield(elem.event, elem.tok, elem.state)
          end
        end
      end
    ensure
      $VERBOSE = verbose
    end

without_circular_ref(obj, seen:, &block) click to toggle source

    # File lib/irb/color.rb, line 156
    def without_circular_ref(obj, seen:, &block)
      return false if seen.key?(obj)
      seen[obj] = true
      block.call
    ensure
      seen.delete(obj)
    end