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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
# frozen_string_literal: true
class Reline::Face
SGR_PARAMETERS = {
foreground: {
black: 30,
red: 31,
green: 32,
yellow: 33,
blue: 34,
magenta: 35,
cyan: 36,
white: 37,
bright_black: 90,
gray: 90,
bright_red: 91,
bright_green: 92,
bright_yellow: 93,
bright_blue: 94,
bright_magenta: 95,
bright_cyan: 96,
bright_white: 97
},
background: {
black: 40,
red: 41,
green: 42,
yellow: 43,
blue: 44,
magenta: 45,
cyan: 46,
white: 47,
bright_black: 100,
gray: 100,
bright_red: 101,
bright_green: 102,
bright_yellow: 103,
bright_blue: 104,
bright_magenta: 105,
bright_cyan: 106,
bright_white: 107,
},
style: {
reset: 0,
bold: 1,
faint: 2,
italicized: 3,
underlined: 4,
slowly_blinking: 5,
blinking: 5,
rapidly_blinking: 6,
negative: 7,
concealed: 8,
crossed_out: 9
}
}.freeze
class Config
ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze
RESET_SGR = "\e[0m".freeze
def initialize(name, &block)
@definition = {}
block.call(self)
ESSENTIAL_DEFINE_NAMES.each do |name|
@definition[name] ||= { style: :reset, escape_sequence: RESET_SGR }
end
end
attr_reader :definition
def define(name, **values)
values[:escape_sequence] = format_to_sgr(values.to_a).freeze
@definition[name] = values
end
def reconfigure
@definition.each_value do |values|
values.delete(:escape_sequence)
values[:escape_sequence] = format_to_sgr(values.to_a).freeze
end
end
def [](name)
@definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}"
end
private
def sgr_rgb(key, value)
return nil unless rgb_expression?(value)
if Reline::Face.truecolor?
sgr_rgb_truecolor(key, value)
else
sgr_rgb_256color(key, value)
end
end
def sgr_rgb_truecolor(key, value)
case key
when :foreground
"38;2;"
when :background
"48;2;"
end + value[1, 6].scan(/../).map(&:hex).join(";")
end
def sgr_rgb_256color(key, value)
# 256 colors are
# 0..15: standard colors, high intensity colors
# 16..232: 216 colors (R, G, B each 6 steps)
# 233..255: grayscale colors (24 steps)
# This methods converts rgb_expression to 216 colors
rgb = value[1, 6].scan(/../).map(&:hex)
# Color steps are [0, 95, 135, 175, 215, 255]
r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 }
color = (16 + 36 * r + 6 * g + b)
case key
when :foreground
"38;5;#{color}"
when :background
"48;5;#{color}"
end
end
def format_to_sgr(ordered_values)
sgr = "\e[" + ordered_values.map do |key_value|
key, value = key_value
case key
when :foreground, :background
case value
when Symbol
SGR_PARAMETERS[key][value]
when String
sgr_rgb(key, value)
end
when :style
[ value ].flatten.map do |style_name|
SGR_PARAMETERS[:style][style_name]
end.then do |sgr_parameters|
sgr_parameters.include?(nil) ? nil : sgr_parameters
end
end.then do |rendition_expression|
unless rendition_expression
raise ArgumentError, "invalid SGR parameter: #{value.inspect}"
end
rendition_expression
end
end.join(';') + "m"
sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr
end
def rgb_expression?(color)
color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/)
end
end
private_constant :SGR_PARAMETERS, :Config
def self.truecolor?
@force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM'])
end
def self.force_truecolor
@force_truecolor = true
@configs&.each_value(&:reconfigure)
end
def self.[](name)
@configs[name]
end
def self.config(name, &block)
@configs ||= {}
@configs[name] = Config.new(name, &block)
end
def self.configs
@configs.transform_values(&:definition)
end
def self.load_initial_configs
config(:default) do |conf|
conf.define :default, style: :reset
conf.define :enhanced, style: :reset
conf.define :scrollbar, style: :reset
end
config(:completion_dialog) do |conf|
conf.define :default, foreground: :bright_white, background: :gray
conf.define :enhanced, foreground: :black, background: :white
conf.define :scrollbar, foreground: :white, background: :gray
end
end
def self.reset_to_initial_configs
@configs = {}
load_initial_configs
end
end
|