[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:
HASUMI Hitoshi 2023-11-06 23:40:38 +09:00 committed by git
parent f6ba87ca88
commit 16403f41ab
8 changed files with 582 additions and 82 deletions

108
doc/reline/face.md Normal file
View 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

View File

@ -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
View 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

View File

@ -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

View File

@ -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
View 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

View File

@ -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'

View File

@ -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