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
|