diff --git a/lib/reline.rb b/lib/reline.rb index bd763db164..47ae8ea235 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -601,15 +601,15 @@ def self.line_editor end Reline::Face.config(:default) do |face| - face.define :default, :default_style - face.define :enhanced, :default_style - face.define :scrollbar, :default_style + face.define :default, style: :default + face.define :enhanced, style: :default + face.define :scrollbar, style: :default end Reline::Face.config(:completion_dialog) do |face| - face.define :default, :white_foreground, :cyan_background - face.define :enhanced, :white_foreground, :magenta_background - face.define :scrollbar, :white_foreground, :cyan_background + face.define :default, foreground: :white, background: :cyan + face.define :enhanced, foreground: :white, background: :magenta + face.define :scrollbar, foreground: :white, background: :cyan end Reline::HISTORY = Reline::History.new(Reline.core.config) diff --git a/lib/reline/face.rb b/lib/reline/face.rb index ee2531dfe6..d9066825d4 100644 --- a/lib/reline/face.rb +++ b/lib/reline/face.rb @@ -1,53 +1,56 @@ class Reline::Face SGR_PARAMETERS = { - # Foreground - black_foreground: 30, - red_foreground: 31, - green_foreground: 32, - yellow_foreground: 33, - blue_foreground: 34, - magenta_foreground: 35, - cyan_foreground: 36, - white_foreground: 37, - bright_black_foreground: 90, - gray_foreground: 90, - bright_red_foreground: 91, - bright_green_foreground: 92, - bright_yellow_foreground: 93, - bright_blue_foreground: 94, - bright_magenta_foreground: 95, - bright_cyan_foreground: 96, - bright_white_foreground: 97, - # Background - black_background: 40, - red_background: 41, - green_background: 42, - yellow_background: 43, - blue_background: 44, - magenta_background: 45, - cyan_background: 46, - white_background: 47, - bright_black_background: 100, - gray_background: 100, - bright_red_background: 101, - bright_green_background: 102, - bright_yellow_background: 103, - bright_blue_background: 104, - bright_magenta_background: 105, - bright_cyan_background: 106, - bright_white_background: 107, - # Style - default_style: 0, - bold: 1, - faint: 2, - italicized: 3, - underlined: 4, - slowly_blinking: 5, - blinking: 5, - rapidly_blinking: 6, - negative: 7, - concealed: 8, - crossed_out: 9 + 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: { + default: 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 FaceConfig @@ -57,25 +60,19 @@ def initialize(name, &block) define(:default) unless self.respond_to?(:default) end - def define(name, *sgr_values) - sgr_values.each do |value| - sgr_valid?(value) or raise ArgumentError, "invalid SGR parameter: #{value.inspect}" - end - sgr = "\e[" + sgr_values.map { |value| - case value - when Symbol - SGR_PARAMETERS[value] - when Hash - key, v = value.first - sgr_rgb(key, v) - end - }.join(';') + "m" + def define(name, foreground: nil, background: nil, style: nil) + values = {} + values[:foreground] = foreground if foreground + values[:background] = background if background + values[:style] = style if style + sgr = format_to_sgr(values) define_singleton_method(name) { sgr } end private def sgr_rgb(key, value) + return nil unless rgb_expression?(value) case key when :foreground "38;2;" @@ -84,21 +81,28 @@ def sgr_rgb(key, value) end + value[1, 6].scan(/../).map(&:hex).join(";") end - def sgr_valid?(sgr_value) - case sgr_value - when Symbol - SGR_PARAMETERS.keys.include?(sgr_value) - when Hash - sgr_value.count == 1 or return false - key, value = sgr_value.first - %i(foreground background).include?(key) or return false - rgb?(value) or return false - else - false - end + def format_to_sgr(values) + "\e[" << values.map do |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 { |style_name| SGR_PARAMETERS[:style][style_name] } + end.then do |rendition_expression| + unless rendition_expression + raise ArgumentError, "invalid SGR parameter: #{value.inspect}" + end + rendition_expression + end + end.join(';') << "m" end - def rgb?(color) + def rgb_expression?(color) color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/) end end diff --git a/test/reline/test_face.rb b/test/reline/test_face.rb index b236e1672e..08e7f44a74 100644 --- a/test/reline/test_face.rb +++ b/test/reline/test_face.rb @@ -4,11 +4,11 @@ class Reline::Face::Test < Reline::TestCase class WithSetupTest < self def setup Reline::Face.config(:my_config) do |face| - face.define :default, :blue_foreground - face.define :enhanced, { foreground: "#FF1020" }, :black_background, :bold, :underlined + face.define :default, foreground: :blue + face.define :enhanced, foreground: "#FF1020", background: :black, style: [:bold, :underlined] end Reline::Face.config(:another_config) do |face| - face.define :another_label, :red_foreground + face.define :another_label, foreground: :red end @face = Reline::Face[:my_config] end @@ -27,21 +27,21 @@ def test_not_respond_to_another_label def test_existing_config_override_default Reline::Face.config(:my_config) do |face| - face.define :default, :red_foreground + face.define :default, foreground: :red end assert_equal "\e[31m", Reline::Face[:my_config].default end def test_existing_config_override_false Reline::Face.config(:my_config, false) do |face| - face.define :default, :red_foreground + face.define :default, foreground: :red end assert_equal "\e[34m", Reline::Face[:my_config].default end def test_new_config_override_false Reline::Face.config(:new_config, false) do |face| - face.define :default, :red_foreground + face.define :default, foreground: :red end assert_equal "\e[31m", Reline::Face[:new_config].default end @@ -56,10 +56,18 @@ def test_my_config_default assert_equal "\e[m", face.default end + def test_invalid_keyword + assert_raise ArgumentError do + Reline::Face.config(:invalid_config) do |face| + face.define :default, invalid_keyword: :red + end + end + end + def test_invalid_foreground_name assert_raise ArgumentError do Reline::Face.config(:invalid_config) do |face| - face.define :default, :invalid_foreground + face.define :default, foreground: :invalid_name end end end @@ -67,7 +75,7 @@ def test_invalid_foreground_name def test_invalid_background_name assert_raise ArgumentError do Reline::Face.config(:invalid_config) do |face| - face.define :default, :invalid_background + face.define :default, background: :invalid_name end end end @@ -85,21 +93,26 @@ def setup end def test_rgb? - assert_equal true, @face_config.send(:rgb?, "#FFFFFF") + assert_equal true, @face_config.send(:rgb_expression?, "#FFFFFF") end def test_invalid_rgb? - assert_equal false, @face_config.send(:rgb?, "FFFFFF") - assert_equal false, @face_config.send(:rgb?, "#FFFFF") + assert_equal false, @face_config.send(:rgb_expression?, "FFFFFF") + assert_equal false, @face_config.send(:rgb_expression?, "#FFFFF") end - def test_sgr_valid? - assert_equal true, @face_config.send(:sgr_valid?, :white_foreground) - assert_equal true, @face_config.send(:sgr_valid?, { foreground: "#ffffff" }) + def test_format_to_sgr + assert_equal( + "\e[37;41;1;3m", + @face_config.send(:format_to_sgr, foreground: :white, background: :red, style: [:bold, :italicized]) + ) end - def test_invalid_sgr_valid? - assert_equal false, @face_config.send(:sgr_valid?, { invalid_key: "#ffffff" }) + def test_format_to_sgr_with_single_style + assert_equal( + "\e[37;41;1m", + @face_config.send(:format_to_sgr, foreground: :white, background: :red, style: :bold) + ) end def test_sgr_rgb