From fdc1d3b1e567939d113f2a78388e7092185d684b Mon Sep 17 00:00:00 2001 From: HASUMI Hitoshi Date: Mon, 6 Nov 2023 23:40:38 +0900 Subject: [PATCH] Introduce a new class Reline::Face to configure character attributes (#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 * Update test/reline/test_face.rb Co-authored-by: Stan Lo * 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 * Update test/reline/test_face.rb Co-authored-by: Stan Lo * 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> --------- Co-authored-by: Stan Lo Co-authored-by: ima1zumi <52617472+ima1zumi@users.noreply.github.com> --- .gitignore | 1 - Gemfile | 1 + README.md | 7 + doc/reline/face.md | 108 +++++++++++ lib/reline.rb | 14 +- lib/reline/face.rb | 157 +++++++++++++++ lib/reline/line_editor.rb | 23 +-- test/reline/helper.rb | 1 + test/reline/test_face.rb | 204 ++++++++++++++++++++ test/reline/yamatanooroti/multiline_repl | 2 +- test/reline/yamatanooroti/test_rendering.rb | 155 +++++++++------ 11 files changed, 590 insertions(+), 83 deletions(-) create mode 100644 doc/reline/face.md create mode 100644 lib/reline/face.rb create mode 100644 test/reline/test_face.rb diff --git a/.gitignore b/.gitignore index 299ab362ec..3e800d33ef 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /.yardoc /_yardoc/ /coverage/ -/doc/ /pkg/ /spec/reports/ /tmp/ diff --git a/Gemfile b/Gemfile index 46494c480c..b4e22bfe7e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,5 +12,6 @@ end gem 'bundler' gem 'rake' gem 'test-unit' +gem 'test-unit-rr' gem 'racc' diff --git a/README.md b/README.md index 84e3286637..4508c42a8f 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,13 @@ end See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl) +## Documentation + +### Reline::Face + +You can modify the text color and text decorations in your terminal emulator. +See [doc/reline/face.md](./doc/reline/face.md) + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/reline. diff --git a/doc/reline/face.md b/doc/reline/face.md new file mode 100644 index 0000000000..cf3bb69440 --- /dev/null +++ b/doc/reline/face.md @@ -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 + diff --git a/lib/reline.rb b/lib/reline.rb index 3d1716c81b..fb19982081 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -7,6 +7,7 @@ require 'reline/line_editor' require 'reline/history' require 'reline/terminfo' +require 'reline/face' require 'rbconfig' module Reline @@ -36,10 +37,8 @@ def match?(other) DialogRenderInfo = Struct.new( :pos, :contents, - :bg_color, - :pointer_bg_color, - :fg_color, - :pointer_fg_color, + :face, + :bg_color, # For the time being, this line should stay here for the compatibility with IRB. :width, :height, :scrollbar, @@ -260,10 +259,7 @@ def get_screen_size contents: result, scrollbar: true, height: [15, preferred_dialog_height].min, - bg_color: 46, - pointer_bg_color: 45, - fg_color: 37, - pointer_fg_color: 37 + face: :completion_dialog ) } Reline::DEFAULT_DIALOG_CONTEXT = Array.new @@ -606,4 +602,6 @@ def self.update_iogate io end +Reline::Face.load_initial_configs + Reline::HISTORY = Reline::History.new(Reline.core.config) diff --git a/lib/reline/face.rb b/lib/reline/face.rb new file mode 100644 index 0000000000..b78f3b1ca5 --- /dev/null +++ b/lib/reline/face.rb @@ -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 diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 0d990c2c0a..d71b903701 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -831,27 +831,24 @@ def add_dialog_proc(name, p, context = nil) dialog.column = 0 dialog.width = @screen_size.last 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| - if i == pointer - 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 + line_sgr = i == pointer ? enhanced_sgr : default_sgr 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) - colored_content = "\e[#{bg_color}m\e[#{fg_color}m#{str}" + colored_content = "#{line_sgr}#{str}" if scrollbar_pos - color_seq = "\e[37m" 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) - 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) - colored_content + color_seq + @lower_half_block + colored_content + scrollbar_sgr + @lower_half_block else - colored_content + color_seq + ' ' * @block_elem_width + colored_content + scrollbar_sgr + ' ' * @block_elem_width end else colored_content diff --git a/test/reline/helper.rb b/test/reline/helper.rb index fb2262e7f5..4d0c883c86 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -4,6 +4,7 @@ require 'reline' require 'test/unit' +require 'test/unit/rr' begin require 'rbconfig' diff --git a/test/reline/test_face.rb b/test/reline/test_face.rb new file mode 100644 index 0000000000..371b4b0d2e --- /dev/null +++ b/test/reline/test_face.rb @@ -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 diff --git a/test/reline/yamatanooroti/multiline_repl b/test/reline/yamatanooroti/multiline_repl index bdefc48faa..e2a900b251 100755 --- a/test/reline/yamatanooroti/multiline_repl +++ b/test/reline/yamatanooroti/multiline_repl @@ -136,7 +136,7 @@ opt.on('--dialog VAL') { |v| if v.include?('alt-scrollbar') scrollbar = true 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') ENV['RELINE_ALT_SCROLLBAR'] = '1' diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb index 8ac0c0c096..670432d86b 100644 --- a/test/reline/yamatanooroti/test_rendering.rb +++ b/test/reline/yamatanooroti/test_rendering.rb @@ -4,6 +4,33 @@ require 'yamatanooroti' 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 @pwd = Dir.pwd suffix = '%010d' % Random.rand(0..65535) @@ -954,75 +981,83 @@ def test_completion_journey_with_empty_line end 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.') - write('a') - write('b') - write('c') - write("\C-h") - close - assert_screen(<<~'EOC') - Multiline REPL. - prompt> ab - Ruby is... - A dynamic, open source programming - language with a focus on simplicity - and productivity. It has an elegant - syntax that is natural to read and - easy to write. - EOC + iterate_over_face_configs do |config_name, config_file| + 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('a') + write('b') + write('c') + write("\C-h") + close + assert_screen(<<~'EOC', "Failed with `#{config_name}` in Face") + Multiline REPL. + prompt> ab + Ruby is... + A dynamic, open source programming + language with a focus on simplicity + and productivity. It has an elegant + syntax that is natural to read and + easy to write. + EOC + end end 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.') - write('a') - write('b') - write('c') - write("\C-h") - close - assert_screen(<<~'EOC') - Multiline REPL. - prompt> ab - Ruby is... - A dynamic, open source programming - language with a focus on simplicity - and productivity. It has an elegant - syntax that is natural to read and - easy to write. - EOC + iterate_over_face_configs do |config_name, config_file| + 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('a') + write('b') + write('c') + write("\C-h") + close + assert_screen(<<~'EOC') + Multiline REPL. + prompt> ab + Ruby is... + A dynamic, open source programming + language with a focus on simplicity + and productivity. It has an elegant + syntax that is natural to read and + easy to write. + EOC + end end 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.') - write("\n" * 10) - write("if 1\n sSt\nend") - write("\C-p\C-h\C-e") - close - assert_screen(<<~'EOC') - prompt> - prompt> - prompt> - prompt> - prompt> - prompt> - prompt> if 1 - prompt> St - prompt> enString - Struct - EOC + iterate_over_face_configs do |config_name, config_file| + 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("\n" * 10) + write("if 1\n sSt\nend") + write("\C-p\C-h\C-e") + close + assert_screen(<<~'EOC') + prompt> + prompt> + prompt> + prompt> + prompt> + prompt> + prompt> if 1 + prompt> St + prompt> enString + Struct + EOC + end end 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.') - write("if 1\n 2\n 3\n 4\n 5\n 6") - write("\C-p\C-n\C-p\C-p\C-p#") - close - assert_screen(<<~'EOC') - prompt> 2 - prompt> 3# - prompt> 4 - prompt> 5 - prompt> 6 Ruby is... - EOC + iterate_over_face_configs do |config_name, config_file| + 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("if 1\n 2\n 3\n 4\n 5\n 6") + write("\C-p\C-n\C-p\C-p\C-p#") + close + assert_screen(<<~'EOC') + prompt> 2 + prompt> 3# + prompt> 4 + prompt> 5 + prompt> 6 Ruby is... + EOC + end end def test_autocomplete_at_bottom