[ruby/reline] Introduce a new class Reline::Face to configure
character attributes (https://github.com/ruby/reline/pull/552) * Reine::Face * fix test_yamatanooroti * Define singleton methods to make accessors to attributes of a face * s/display/foreground/ * s/default/default_style/ && s/normal_line/default/ && s/enhanced_line/enhanced/ * fix typo * FaceConfig.new now takes keyword arguments * Update lib/reline/face.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Update test/reline/test_face.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Fix to correspond to frozen_string_literal * Face::FaceConfig -> Face::Config * ref https://github.com/ruby/reline/pull/552#pullrequestreview-1677282576 * delete unused ivar * ref https://github.com/ruby/reline/pull/552#discussion_r1358783723 * insert "\e[0m" into all SGR * tiny fix * ESSENTIAL_DEFINE_NAMES ref https://github.com/ruby/reline/pull/552#discussion_r1367722247 * Change to Hash-accessor style - Reline::Face[:completion_dialog].enhanced -> Reline::Face[:completion_dialog][:enhanced] - Reline::Face.configs shows all defined values * Cache array method call in local variable * Tests for Face configuration variations * resolve https://github.com/ruby/reline/pull/552#pullrequestreview-1710938154 * amend to * check invalid SGR parameter in :style * The order of define values should be preserved * Update test/reline/test_face.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Update test/reline/test_face.rb Co-authored-by: Stan Lo <stan001212@gmail.com> * Add methods: load_initial_config and reset_to_initial_config. And teardown in tests * omission in amending "style: :default" to "style: :reset" * refs https://github.com/ruby/reline/issues/598 * Fix link * amend method name * Update lib/reline/face.rb Co-authored-by: ima1zumi <52617472+ima1zumi@users.noreply.github.com> --------- https://github.com/ruby/reline/commit/fdc1d3b1e5 Co-authored-by: Stan Lo <stan001212@gmail.com> Co-authored-by: ima1zumi <52617472+ima1zumi@users.noreply.github.com>
This commit is contained in:
parent
f6ba87ca88
commit
16403f41ab
108
doc/reline/face.md
Normal file
108
doc/reline/face.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# Face
|
||||||
|
|
||||||
|
With the `Reline::Face` class, you can modify the text color and text decorations in your terminal emulator.
|
||||||
|
This is primarily used to customize the appearance of the method completion dialog in IRB.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### ex: Change the background color of the completion dialog cyan to blue
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
Reline::Face.config(:completion_dialog) do |conf|
|
||||||
|
conf.define :default, foreground: :white, background: :blue
|
||||||
|
# ^^^^^ `:cyan` by default
|
||||||
|
conf.define :enhanced, foreground: :white, background: :magenta
|
||||||
|
conf.define :scrollbar, foreground: :white, background: :blue
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you provide the above code to an IRB session in some way, you can apply the configuration.
|
||||||
|
It's generally done by writing it in `.irbrc`.
|
||||||
|
|
||||||
|
Regarding `.irbrc`, please refer to the following link: [https://docs.ruby-lang.org/en/master/IRB.html](https://docs.ruby-lang.org/en/master/IRB.html)
|
||||||
|
|
||||||
|
## Available parameters
|
||||||
|
|
||||||
|
`Reline::Face` internally creates SGR (Select Graphic Rendition) code according to the block parameter of `Reline::Face.config` method.
|
||||||
|
|
||||||
|
| Key | Value | SGR Code (numeric part following "\e[")|
|
||||||
|
|:------------|:------------------|-----:|
|
||||||
|
| :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 |
|
||||||
|
|
||||||
|
- The value for `:style` can be both a Symbol and an Array
|
||||||
|
```ruby
|
||||||
|
# Single symbol
|
||||||
|
conf.define :default, style: :bold
|
||||||
|
# Array
|
||||||
|
conf.define :default, style: [:bold, :negative]
|
||||||
|
```
|
||||||
|
- The availability of specific SGR codes depends on your terminal emulator
|
||||||
|
- You can specify a hex color code to `:foreground` and `:background` color like `foreground: "#FF1020"`. Its availability also depends on your terminal emulator
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
You can see the current Face configuration by `Reline::Face.configs` method
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
irb(main):001:0> Reline::Face.configs
|
||||||
|
=>
|
||||||
|
{:default=>
|
||||||
|
{:default=>{:style=>:reset, :escape_sequence=>"\e[0m"},
|
||||||
|
:enhanced=>{:style=>:reset, :escape_sequence=>"\e[0m"},
|
||||||
|
:scrollbar=>{:style=>:reset, :escape_sequence=>"\e[0m"}},
|
||||||
|
:completion_dialog=>
|
||||||
|
{:default=>{:foreground=>:white, :background=>:cyan, :escape_sequence=>"\e[0m\e[37;46m"},
|
||||||
|
:enhanced=>{:foreground=>:white, :background=>:magenta, :escape_sequence=>"\e[0m\e[37;45m"},
|
||||||
|
:scrollbar=>{:foreground=>:white, :background=>:cyan, :escape_sequence=>"\e[0m\e[37;46m"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backlog
|
||||||
|
|
||||||
|
- Support for 256-color terminal emulator. Fallback hex color code such as "#FF1020" to 256 colors
|
||||||
|
|
@ -7,6 +7,7 @@ require 'reline/key_stroke'
|
|||||||
require 'reline/line_editor'
|
require 'reline/line_editor'
|
||||||
require 'reline/history'
|
require 'reline/history'
|
||||||
require 'reline/terminfo'
|
require 'reline/terminfo'
|
||||||
|
require 'reline/face'
|
||||||
require 'rbconfig'
|
require 'rbconfig'
|
||||||
|
|
||||||
module Reline
|
module Reline
|
||||||
@ -36,10 +37,8 @@ module Reline
|
|||||||
DialogRenderInfo = Struct.new(
|
DialogRenderInfo = Struct.new(
|
||||||
:pos,
|
:pos,
|
||||||
:contents,
|
:contents,
|
||||||
:bg_color,
|
:face,
|
||||||
:pointer_bg_color,
|
:bg_color, # For the time being, this line should stay here for the compatibility with IRB.
|
||||||
:fg_color,
|
|
||||||
:pointer_fg_color,
|
|
||||||
:width,
|
:width,
|
||||||
:height,
|
:height,
|
||||||
:scrollbar,
|
:scrollbar,
|
||||||
@ -260,10 +259,7 @@ module Reline
|
|||||||
contents: result,
|
contents: result,
|
||||||
scrollbar: true,
|
scrollbar: true,
|
||||||
height: [15, preferred_dialog_height].min,
|
height: [15, preferred_dialog_height].min,
|
||||||
bg_color: 46,
|
face: :completion_dialog
|
||||||
pointer_bg_color: 45,
|
|
||||||
fg_color: 37,
|
|
||||||
pointer_fg_color: 37
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
|
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
|
||||||
@ -606,4 +602,6 @@ else
|
|||||||
io
|
io
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reline::Face.load_initial_configs
|
||||||
|
|
||||||
Reline::HISTORY = Reline::History.new(Reline.core.config)
|
Reline::HISTORY = Reline::History.new(Reline.core.config)
|
||||||
|
157
lib/reline/face.rb
Normal file
157
lib/reline/face.rb
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# 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 [](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)
|
||||||
|
case key
|
||||||
|
when :foreground
|
||||||
|
"38;2;"
|
||||||
|
when :background
|
||||||
|
"48;2;"
|
||||||
|
end + value[1, 6].scan(/../).map(&:hex).join(";")
|
||||||
|
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.[](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: :white, background: :cyan
|
||||||
|
conf.define :enhanced, foreground: :white, background: :magenta
|
||||||
|
conf.define :scrollbar, foreground: :white, background: :cyan
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.reset_to_initial_configs
|
||||||
|
@configs = {}
|
||||||
|
load_initial_configs
|
||||||
|
end
|
||||||
|
end
|
@ -831,27 +831,24 @@ class Reline::LineEditor
|
|||||||
dialog.column = 0
|
dialog.column = 0
|
||||||
dialog.width = @screen_size.last
|
dialog.width = @screen_size.last
|
||||||
end
|
end
|
||||||
|
face = Reline::Face[dialog_render_info.face || :default]
|
||||||
|
scrollbar_sgr = face[:scrollbar]
|
||||||
|
default_sgr = face[:default]
|
||||||
|
enhanced_sgr = face[:enhanced]
|
||||||
dialog.contents = contents.map.with_index do |item, i|
|
dialog.contents = contents.map.with_index do |item, i|
|
||||||
if i == pointer
|
line_sgr = i == pointer ? enhanced_sgr : default_sgr
|
||||||
fg_color = dialog_render_info.pointer_fg_color
|
|
||||||
bg_color = dialog_render_info.pointer_bg_color
|
|
||||||
else
|
|
||||||
fg_color = dialog_render_info.fg_color
|
|
||||||
bg_color = dialog_render_info.bg_color
|
|
||||||
end
|
|
||||||
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
|
||||||
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
|
||||||
colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}"
|
colored_content = "#{line_sgr}#{str}"
|
||||||
if scrollbar_pos
|
if scrollbar_pos
|
||||||
color_seq = "\e[37m"
|
|
||||||
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
|
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
|
||||||
colored_content + color_seq + @full_block
|
colored_content + scrollbar_sgr + @full_block
|
||||||
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
|
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
|
||||||
colored_content + color_seq + @upper_half_block
|
colored_content + scrollbar_sgr + @upper_half_block
|
||||||
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
|
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
|
||||||
colored_content + color_seq + @lower_half_block
|
colored_content + scrollbar_sgr + @lower_half_block
|
||||||
else
|
else
|
||||||
colored_content + color_seq + ' ' * @block_elem_width
|
colored_content + scrollbar_sgr + ' ' * @block_elem_width
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
colored_content
|
colored_content
|
||||||
|
@ -4,6 +4,7 @@ ENV['TERM'] = 'xterm' # for some CI environments
|
|||||||
|
|
||||||
require 'reline'
|
require 'reline'
|
||||||
require 'test/unit'
|
require 'test/unit'
|
||||||
|
require 'test/unit/rr'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'rbconfig'
|
require 'rbconfig'
|
||||||
|
204
test/reline/test_face.rb
Normal file
204
test/reline/test_face.rb
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'helper'
|
||||||
|
|
||||||
|
class Reline::Face::Test < Reline::TestCase
|
||||||
|
RESET_SGR = "\e[0m"
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Reline::Face.reset_to_initial_configs
|
||||||
|
end
|
||||||
|
|
||||||
|
class WithInsufficientSetupTest < self
|
||||||
|
def setup
|
||||||
|
Reline::Face.config(:my_insufficient_config) do |face|
|
||||||
|
end
|
||||||
|
@face = Reline::Face[:my_insufficient_config]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_my_insufficient_config_line
|
||||||
|
assert_equal RESET_SGR, @face[:default]
|
||||||
|
assert_equal RESET_SGR, @face[:enhanced]
|
||||||
|
assert_equal RESET_SGR, @face[:scrollbar]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_my_insufficient_configs
|
||||||
|
my_configs = Reline::Face.configs[:my_insufficient_config]
|
||||||
|
assert_equal(
|
||||||
|
{
|
||||||
|
default: { style: :reset, escape_sequence: RESET_SGR },
|
||||||
|
enhanced: { style: :reset, escape_sequence: RESET_SGR },
|
||||||
|
scrollbar: { style: :reset, escape_sequence: RESET_SGR }
|
||||||
|
},
|
||||||
|
my_configs
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class WithSetupTest < self
|
||||||
|
def setup
|
||||||
|
Reline::Face.config(:my_config) do |face|
|
||||||
|
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, foreground: :red
|
||||||
|
end
|
||||||
|
@face = Reline::Face[:my_config]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_now_there_are_four_configs
|
||||||
|
assert_equal %i(default completion_dialog my_config another_config), Reline::Face.configs.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_resetting_config_discards_user_defined_configs
|
||||||
|
Reline::Face.reset_to_initial_configs
|
||||||
|
assert_equal %i(default completion_dialog), Reline::Face.configs.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_my_configs
|
||||||
|
my_configs = Reline::Face.configs[:my_config]
|
||||||
|
assert_equal(
|
||||||
|
{
|
||||||
|
default: {
|
||||||
|
escape_sequence: "#{RESET_SGR}\e[34m", foreground: :blue
|
||||||
|
},
|
||||||
|
enhanced: {
|
||||||
|
background: :black,
|
||||||
|
foreground: "#FF1020",
|
||||||
|
style: [:bold, :underlined],
|
||||||
|
escape_sequence: "\e[0m\e[38;2;255;16;32;40;1;4m"
|
||||||
|
},
|
||||||
|
scrollbar: {
|
||||||
|
style: :reset,
|
||||||
|
escape_sequence: "\e[0m"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
my_configs
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_my_config_line
|
||||||
|
assert_equal "#{RESET_SGR}\e[34m", @face[:default]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_my_config_enhanced
|
||||||
|
assert_equal "#{RESET_SGR}\e[38;2;255;16;32;40;1;4m", @face[:enhanced]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_not_respond_to_another_label
|
||||||
|
assert_equal false, @face.respond_to?(:another_label)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class WithoutSetupTest < self
|
||||||
|
def test_my_config_default
|
||||||
|
Reline::Face.config(:my_config) do |face|
|
||||||
|
# do nothing
|
||||||
|
end
|
||||||
|
face = Reline::Face[:my_config]
|
||||||
|
assert_equal RESET_SGR, face[:default]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_style_does_not_exist
|
||||||
|
face = Reline::Face[:default]
|
||||||
|
assert_raise ArgumentError do
|
||||||
|
face[:style_does_not_exist]
|
||||||
|
end
|
||||||
|
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, foreground: :invalid_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_background_name
|
||||||
|
assert_raise ArgumentError do
|
||||||
|
Reline::Face.config(:invalid_config) do |face|
|
||||||
|
face.define :default, background: :invalid_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_style_name
|
||||||
|
assert_raise ArgumentError do
|
||||||
|
Reline::Face.config(:invalid_config) do |face|
|
||||||
|
face.define :default, style: :invalid_name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_private_constants
|
||||||
|
[:SGR_PARAMETER, :Config, :CONFIGS].each do |name|
|
||||||
|
assert_equal false, Reline::Face.constants.include?(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class ConfigTest < self
|
||||||
|
def setup
|
||||||
|
@config = Reline::Face.const_get(:Config).new(:my_config) { }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_the_order_of_define_values_should_be_preserved
|
||||||
|
any_instance_of(Reline::Face.const_get(:Config)) do |config|
|
||||||
|
mock(config).format_to_sgr(
|
||||||
|
[[:foreground, :blue], [:style, [:bold, :italicized]], [:background, :red]]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
Reline::Face.config(:my_config) do |face|
|
||||||
|
face.define :default, foreground: :blue, style: [:bold, :italicized], background: :red
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_rgb?
|
||||||
|
assert_equal true, @config.send(:rgb_expression?, "#FFFFFF")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_invalid_rgb?
|
||||||
|
assert_equal false, @config.send(:rgb_expression?, "FFFFFF")
|
||||||
|
assert_equal false, @config.send(:rgb_expression?, "#FFFFF")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_format_to_sgr
|
||||||
|
assert_equal(
|
||||||
|
"#{RESET_SGR}\e[37;41;1;3m",
|
||||||
|
@config.send(:format_to_sgr, foreground: :white, background: :red, style: [:bold, :italicized])
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_format_to_sgr_with_reset
|
||||||
|
assert_equal(
|
||||||
|
RESET_SGR,
|
||||||
|
@config.send(:format_to_sgr, style: :reset)
|
||||||
|
)
|
||||||
|
assert_equal(
|
||||||
|
"#{RESET_SGR}\e[37;0;41m",
|
||||||
|
@config.send(:format_to_sgr, foreground: :white, style: :reset, background: :red)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_format_to_sgr_with_single_style
|
||||||
|
assert_equal(
|
||||||
|
"#{RESET_SGR}\e[37;41;1m",
|
||||||
|
@config.send(:format_to_sgr, foreground: :white, background: :red, style: :bold)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_sgr_rgb
|
||||||
|
assert_equal "38;2;255;255;255", @config.send(:sgr_rgb, :foreground, "#ffffff")
|
||||||
|
assert_equal "48;2;18;52;86", @config.send(:sgr_rgb, :background, "#123456")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -136,7 +136,7 @@ opt.on('--dialog VAL') { |v|
|
|||||||
if v.include?('alt-scrollbar')
|
if v.include?('alt-scrollbar')
|
||||||
scrollbar = true
|
scrollbar = true
|
||||||
end
|
end
|
||||||
Reline::DialogRenderInfo.new(pos: cursor_pos, contents: contents, height: height, scrollbar: scrollbar)
|
Reline::DialogRenderInfo.new(pos: cursor_pos, contents: contents, height: height, scrollbar: scrollbar, face: :completion_dialog)
|
||||||
})
|
})
|
||||||
if v.include?('alt-scrollbar')
|
if v.include?('alt-scrollbar')
|
||||||
ENV['RELINE_ALT_SCROLLBAR'] = '1'
|
ENV['RELINE_ALT_SCROLLBAR'] = '1'
|
||||||
|
@ -4,6 +4,33 @@ begin
|
|||||||
require 'yamatanooroti'
|
require 'yamatanooroti'
|
||||||
|
|
||||||
class Reline::RenderingTest < Yamatanooroti::TestCase
|
class Reline::RenderingTest < Yamatanooroti::TestCase
|
||||||
|
|
||||||
|
FACE_CONFIGS = { no_config: "", valid_config: <<~VALID_CONFIG, incomplete_config: <<~INCOMPLETE_CONFIG }
|
||||||
|
require "reline"
|
||||||
|
Reline::Face.config(:completion_dialog) do |face|
|
||||||
|
face.define :default, foreground: :white, background: :blue
|
||||||
|
face.define :enhanced, foreground: :white, background: :magenta
|
||||||
|
face.define :scrollbar, foreground: :white, background: :blue
|
||||||
|
end
|
||||||
|
VALID_CONFIG
|
||||||
|
require "reline"
|
||||||
|
Reline::Face.config(:completion_dialog) do |face|
|
||||||
|
face.define :default, foreground: :white, background: :black
|
||||||
|
face.define :scrollbar, foreground: :white, background: :cyan
|
||||||
|
end
|
||||||
|
INCOMPLETE_CONFIG
|
||||||
|
|
||||||
|
def iterate_over_face_configs(&block)
|
||||||
|
FACE_CONFIGS.each do |config_name, face_config|
|
||||||
|
config_file = Tempfile.create(%w{face_config- .rb})
|
||||||
|
config_file.write face_config
|
||||||
|
block.call(config_name, config_file)
|
||||||
|
config_file.close
|
||||||
|
ensure
|
||||||
|
File.delete(config_file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
@pwd = Dir.pwd
|
@pwd = Dir.pwd
|
||||||
suffix = '%010d' % Random.rand(0..65535)
|
suffix = '%010d' % Random.rand(0..65535)
|
||||||
@ -954,75 +981,83 @@ begin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_simple_dialog
|
def test_simple_dialog
|
||||||
start_terminal(20, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
iterate_over_face_configs do |config_name, config_file|
|
||||||
write('a')
|
start_terminal(20, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
||||||
write('b')
|
write('a')
|
||||||
write('c')
|
write('b')
|
||||||
write("\C-h")
|
write('c')
|
||||||
close
|
write("\C-h")
|
||||||
assert_screen(<<~'EOC')
|
close
|
||||||
Multiline REPL.
|
assert_screen(<<~'EOC', "Failed with `#{config_name}` in Face")
|
||||||
prompt> ab
|
Multiline REPL.
|
||||||
Ruby is...
|
prompt> ab
|
||||||
A dynamic, open source programming
|
Ruby is...
|
||||||
language with a focus on simplicity
|
A dynamic, open source programming
|
||||||
and productivity. It has an elegant
|
language with a focus on simplicity
|
||||||
syntax that is natural to read and
|
and productivity. It has an elegant
|
||||||
easy to write.
|
syntax that is natural to read and
|
||||||
EOC
|
easy to write.
|
||||||
|
EOC
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_simple_dialog_at_right_edge
|
def test_simple_dialog_at_right_edge
|
||||||
start_terminal(20, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
iterate_over_face_configs do |config_name, config_file|
|
||||||
write('a')
|
start_terminal(20, 40, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
||||||
write('b')
|
write('a')
|
||||||
write('c')
|
write('b')
|
||||||
write("\C-h")
|
write('c')
|
||||||
close
|
write("\C-h")
|
||||||
assert_screen(<<~'EOC')
|
close
|
||||||
Multiline REPL.
|
assert_screen(<<~'EOC')
|
||||||
prompt> ab
|
Multiline REPL.
|
||||||
Ruby is...
|
prompt> ab
|
||||||
A dynamic, open source programming
|
Ruby is...
|
||||||
language with a focus on simplicity
|
A dynamic, open source programming
|
||||||
and productivity. It has an elegant
|
language with a focus on simplicity
|
||||||
syntax that is natural to read and
|
and productivity. It has an elegant
|
||||||
easy to write.
|
syntax that is natural to read and
|
||||||
EOC
|
easy to write.
|
||||||
|
EOC
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_dialog_scroll_pushup_condition
|
def test_dialog_scroll_pushup_condition
|
||||||
start_terminal(10, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
|
iterate_over_face_configs do |config_name, config_file|
|
||||||
write("\n" * 10)
|
start_terminal(10, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --autocomplete}, startup_message: 'Multiline REPL.')
|
||||||
write("if 1\n sSt\nend")
|
write("\n" * 10)
|
||||||
write("\C-p\C-h\C-e")
|
write("if 1\n sSt\nend")
|
||||||
close
|
write("\C-p\C-h\C-e")
|
||||||
assert_screen(<<~'EOC')
|
close
|
||||||
prompt>
|
assert_screen(<<~'EOC')
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt>
|
prompt>
|
||||||
prompt> if 1
|
prompt>
|
||||||
prompt> St
|
prompt> if 1
|
||||||
prompt> enString
|
prompt> St
|
||||||
Struct
|
prompt> enString
|
||||||
EOC
|
Struct
|
||||||
|
EOC
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_simple_dialog_with_scroll_screen
|
def test_simple_dialog_with_scroll_screen
|
||||||
start_terminal(5, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
iterate_over_face_configs do |config_name, config_file|
|
||||||
write("if 1\n 2\n 3\n 4\n 5\n 6")
|
start_terminal(5, 50, %W{ruby -I#{@pwd}/lib -r#{config_file.path} #{@pwd}/test/reline/yamatanooroti/multiline_repl --dialog simple}, startup_message: 'Multiline REPL.')
|
||||||
write("\C-p\C-n\C-p\C-p\C-p#")
|
write("if 1\n 2\n 3\n 4\n 5\n 6")
|
||||||
close
|
write("\C-p\C-n\C-p\C-p\C-p#")
|
||||||
assert_screen(<<~'EOC')
|
close
|
||||||
prompt> 2
|
assert_screen(<<~'EOC')
|
||||||
prompt> 3#
|
prompt> 2
|
||||||
prompt> 4
|
prompt> 3#
|
||||||
prompt> 5
|
prompt> 4
|
||||||
prompt> 6 Ruby is...
|
prompt> 5
|
||||||
EOC
|
prompt> 6 Ruby is...
|
||||||
|
EOC
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_autocomplete_at_bottom
|
def test_autocomplete_at_bottom
|
||||||
|
Loading…
x
Reference in New Issue
Block a user