summaryrefslogtreecommitdiff
path: root/lib/irb/color.rb
blob: 150e9e56664bf9242811b9d2b8858e57ea1edbc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# frozen_string_literal: true
require 'ripper'

module IRB # :nodoc:
  module Color
    CLEAR     = 0
    BOLD      = 1
    UNDERLINE = 4
    RED       = 31
    GREEN     = 32
    BLUE      = 34
    MAGENTA   = 35
    CYAN      = 36

    TOKEN_KEYWORDS = {
      on_kw: ['nil', 'self', 'true', 'false'],
      on_const: ['ENV'],
    }

    TOKEN_SEQ_EXPRS = {
      on_CHAR:            [[BLUE, BOLD],            [Ripper::EXPR_END]],
      on_const:           [[BLUE, BOLD, UNDERLINE], [Ripper::EXPR_ARG, Ripper::EXPR_CMDARG]],
      on_embexpr_beg:     [[RED],                   [Ripper::EXPR_BEG, Ripper::EXPR_END]],
      on_embexpr_end:     [[RED],                   [Ripper::EXPR_END, Ripper::EXPR_ENDFN]],
      on_ident:           [[BLUE, BOLD],            [Ripper::EXPR_ENDFN]],
      on_int:             [[BLUE, BOLD],            [Ripper::EXPR_END]],
      on_float:           [[MAGENTA, BOLD],         [Ripper::EXPR_END]],
      on_kw:              [[GREEN],                 [Ripper::EXPR_CLASS, Ripper::EXPR_BEG, Ripper::EXPR_END, Ripper::EXPR_FNAME]],
      on_label:           [[MAGENTA],               [Ripper::EXPR_LABELED]],
      on_qwords_beg:      [[RED],                   [Ripper::EXPR_BEG]],
      on_regexp_beg:      [[RED],                   [Ripper::EXPR_BEG]],
      on_regexp_end:      [[RED],                   [Ripper::EXPR_BEG]],
      on_symbeg:          [[BLUE, BOLD],            [Ripper::EXPR_FNAME]],
      on_tstring_beg:     [[RED],                   [Ripper::EXPR_BEG, Ripper::EXPR_END, Ripper::EXPR_ARG, Ripper::EXPR_CMDARG]],
      on_tstring_content: [[RED],                   [Ripper::EXPR_BEG, Ripper::EXPR_ARG, Ripper::EXPR_CMDARG]],
      on_tstring_end:     [[RED],                   [Ripper::EXPR_END]],
    }

    class << self
      def colorable?
        $stdout.tty? && ENV.key?('TERM')
      end

      def clear
        return '' unless colorable?
        "\e[#{CLEAR}m"
      end

      def colorize(text, seq)
        return text unless colorable?
        "#{seq.map { |s| "\e[#{const_get(s)}m" }.join('')}#{text}#{clear}"
      end

      def colorize_code(code)
        return code unless colorable?

        colored = +''
        Ripper.lex(code).each do |(_line, _col), token, str, expr|
          if seq = dispatch_seq(token, expr, str)
            colored << "#{seq.map { |s| "\e[#{s}m" }.join('')}#{str}#{clear}"
          else
            colored << str
          end
        end
        colored
      end

      private

      def dispatch_seq(token, expr, str)
        if token == :on_comment
          [BLUE, BOLD]
        elsif TOKEN_KEYWORDS.fetch(token, []).include?(str)
          [CYAN, BOLD]
        elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; exprs&.any? { |e| (expr & e) != Ripper::EXPR_NONE })
          seq
        else
          nil
        end
      end
    end
  end
end