summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraycabta <aycabta@gmail.com>2020-09-16 11:19:06 +0900
committernagachika <nagachika@ruby-lang.org>2020-09-16 21:07:25 +0900
commit3bb503e0e8f92c039ce50f430b14649a36c03feb (patch)
treecf88e25a2d373ed72fa46c4998de5fa12abd1f97
parented39078d37e16b541d717cd87cacb21aa33e6ef1 (diff)
Merge Reline 0.1.5
-rw-r--r--lib/reline.rb16
-rw-r--r--lib/reline/ansi.rb39
-rw-r--r--lib/reline/config.rb68
-rw-r--r--lib/reline/history.rb42
-rw-r--r--lib/reline/key_actor/emacs.rb12
-rw-r--r--lib/reline/key_actor/vi_command.rb4
-rw-r--r--lib/reline/key_actor/vi_insert.rb4
-rw-r--r--lib/reline/key_stroke.rb2
-rw-r--r--lib/reline/line_editor.rb249
-rw-r--r--lib/reline/reline.gemspec2
-rw-r--r--lib/reline/unicode.rb68
-rw-r--r--lib/reline/version.rb2
-rw-r--r--lib/reline/windows.rb15
-rw-r--r--test/reline/test_config.rb107
-rw-r--r--test/reline/test_history.rb20
-rw-r--r--test/reline/test_key_actor_emacs.rb154
-rw-r--r--test/reline/test_key_stroke.rb19
-rw-r--r--test/reline/test_reline.rb38
-rw-r--r--test/reline/test_within_pipe.rb13
-rw-r--r--test/reline/yamatanooroti/test_rendering.rb193
20 files changed, 914 insertions, 153 deletions
diff --git a/lib/reline.rb b/lib/reline.rb
index 1537ee7e69..eb18d0d075 100644
--- a/lib/reline.rb
+++ b/lib/reline.rb
@@ -7,6 +7,7 @@ require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'
require 'reline/history'
+require 'rbconfig'
module Reline
FILENAME_COMPLETION_PROC = nil
@@ -98,22 +99,22 @@ module Reline
end
def completion_proc=(p)
- raise ArgumentError unless p.respond_to?(:call)
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
@completion_proc = p
end
def output_modifier_proc=(p)
- raise ArgumentError unless p.respond_to?(:call)
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
@output_modifier_proc = p
end
def prompt_proc=(p)
- raise ArgumentError unless p.respond_to?(:call)
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
@prompt_proc = p
end
def auto_indent_proc=(p)
- raise ArgumentError unless p.respond_to?(:call)
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
@auto_indent_proc = p
end
@@ -122,7 +123,7 @@ module Reline
end
def dig_perfect_match_proc=(p)
- raise ArgumentError unless p.respond_to?(:call)
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
@dig_perfect_match_proc = p
end
@@ -222,7 +223,6 @@ module Reline
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
line_editor.pre_input_hook = pre_input_hook
- line_editor.rerender
unless config.test_mode
config.read
@@ -232,6 +232,8 @@ module Reline
end
end
+ line_editor.rerender
+
begin
loop do
read_io(config.keyseq_timeout) { |inputs|
@@ -243,6 +245,8 @@ module Reline
break if line_editor.finished?
end
Reline::IOGate.move_cursor_column(0)
+ rescue Errno::EIO
+ # Maybe the I/O has been closed.
rescue StandardError => e
line_editor.finalize
Reline::IOGate.deprep(otio)
diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb
index 3ef02d6e7a..80fccd74f9 100644
--- a/lib/reline/ansi.rb
+++ b/lib/reline/ansi.rb
@@ -28,12 +28,22 @@ class Reline::ANSI
[27, 71, 67] => :ed_next_char, # →
[27, 71, 68] => :ed_prev_char, # ←
+ # urxvt / exoterm
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
+ [27, 91, 56, 126] => :ed_move_to_end, # End
+
# GNOME
[27, 79, 72] => :ed_move_to_beg, # Home
[27, 79, 70] => :ed_move_to_end, # End
# Del is 0x08
# Arrow keys are the same of KDE
+ # iTerm2
+ [27, 27, 91, 67] => :em_next_word, # Option+→
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←
+ [195, 166] => :em_next_word, # Option+f
+ [195, 162] => :ed_prev_word, # Option+b
+
# others
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
@@ -61,8 +71,13 @@ class Reline::ANSI
unless @@buf.empty?
return @@buf.shift
end
- c = @@input.raw(intr: true, &:getbyte)
+ until c = @@input.raw(intr: true, &:getbyte)
+ sleep 0.1
+ end
(c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
+ rescue Errno::EIO
+ # Maybe the I/O has been closed.
+ nil
end
def self.ungetc(c)
@@ -105,10 +120,13 @@ class Reline::ANSI
@@input.raw do |stdin|
@@output << "\e[6n"
@@output.flush
- while (c = stdin.getc) != 'R'
- res << c if c
+ loop do
+ c = stdin.getc
+ next if c.nil?
+ res << c
+ m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
+ break if m
end
- m = res.match(/\e\[(?<row>\d+);(?<column>\d+)/)
(m.pre_match + m.post_match).chars.reverse_each do |ch|
stdin.ungetc ch
end
@@ -116,9 +134,16 @@ class Reline::ANSI
column = m[:column].to_i - 1
row = m[:row].to_i - 1
rescue Errno::ENOTTY
- buf = @@output.pread(@@output.pos, 0)
- row = buf.count("\n")
- column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
+ begin
+ buf = @@output.pread(@@output.pos, 0)
+ row = buf.count("\n")
+ column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
+ rescue Errno::ESPIPE
+ # Just returns column 1 for ambiguous width because this I/O is not
+ # tty and can't seek.
+ row = 0
+ column = 1
+ end
end
Reline::CursorPos.new(column, row)
end
diff --git a/lib/reline/config.rb b/lib/reline/config.rb
index 53b868fd2e..370d100414 100644
--- a/lib/reline/config.rb
+++ b/lib/reline/config.rb
@@ -1,10 +1,6 @@
-require 'pathname'
-
class Reline::Config
attr_reader :test_mode
- DEFAULT_PATH = '~/.inputrc'
-
KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
class InvalidInputrc < RuntimeError
@@ -37,6 +33,10 @@ class Reline::Config
show-all-if-ambiguous
show-all-if-unmodified
visible-stats
+ show-mode-in-prompt
+ vi-cmd-mode-icon
+ vi-ins-mode-icon
+ emacs-mode-string
}
VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
VARIABLE_NAME_SYMBOLS.each do |v|
@@ -54,7 +54,11 @@ class Reline::Config
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
- @history_size = 500
+ @vi_cmd_mode_icon = '(cmd)'
+ @vi_ins_mode_icon = '(ins)'
+ @emacs_mode_string = '@'
+ # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
+ @history_size = -1 # unlimited
@keyseq_timeout = 500
@test_mode = false
end
@@ -86,14 +90,31 @@ class Reline::Config
def inputrc_path
case ENV['INPUTRC']
when nil, ''
- DEFAULT_PATH
else
- ENV['INPUTRC']
+ return File.expand_path(ENV['INPUTRC'])
end
+
+ # In the XDG Specification, if ~/.config/readline/inputrc exists, then
+ # ~/.inputrc should not be read, but for compatibility with GNU Readline,
+ # if ~/.inputrc exists, then it is given priority.
+ home_rc_path = File.expand_path('~/.inputrc')
+ return home_rc_path if File.exist?(home_rc_path)
+
+ case path = ENV['XDG_CONFIG_HOME']
+ when nil, ''
+ else
+ path = File.join(path, 'readline/inputrc')
+ return path if File.exist?(path) and path == File.expand_path(path)
+ end
+
+ path = File.expand_path('~/.config/readline/inputrc')
+ return path if File.exist?(path)
+
+ return home_rc_path
end
def read(file = nil)
- file ||= File.expand_path(inputrc_path)
+ file ||= inputrc_path
begin
if file.respond_to?(:readlines)
lines = file.readlines
@@ -144,7 +165,7 @@ class Reline::Config
case line
when /^set +([^ ]+) +([^ ]+)/i
- var, value = $1.downcase, $2.downcase
+ var, value = $1.downcase, $2
bind_variable(var, value)
next
when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
@@ -194,7 +215,11 @@ class Reline::Config
def bind_variable(name, value)
case name
when 'history-size'
- @history_size = value.to_i
+ begin
+ @history_size = Integer(value)
+ rescue ArgumentError
+ @history_size = 500
+ end
when 'bell-style'
@bell_style =
case value
@@ -233,12 +258,35 @@ class Reline::Config
end
when 'keyseq-timeout'
@keyseq_timeout = value.to_i
+ when 'show-mode-in-prompt'
+ case value
+ when 'off'
+ @show_mode_in_prompt = false
+ when 'on'
+ @show_mode_in_prompt = true
+ else
+ @show_mode_in_prompt = false
+ end
+ when 'vi-cmd-mode-string'
+ @vi_cmd_mode_icon = retrieve_string(value)
+ when 'vi-ins-mode-string'
+ @vi_ins_mode_icon = retrieve_string(value)
+ when 'emacs-mode-string'
+ @emacs_mode_string = retrieve_string(value)
when *VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
end
end
+ def retrieve_string(str)
+ if str =~ /\A"(.*)"\z/
+ parse_keyseq($1).map(&:chr).join
+ else
+ parse_keyseq(str).map(&:chr).join
+ end
+ end
+
def bind_key(key, func_name)
if key =~ /\A"(.*)"\z/
keyseq = parse_keyseq($1)
diff --git a/lib/reline/history.rb b/lib/reline/history.rb
index d95f1cebc3..7a1ed6b90b 100644
--- a/lib/reline/history.rb
+++ b/lib/reline/history.rb
@@ -29,27 +29,47 @@ class Reline::History < Array
end
def push(*val)
- diff = size + val.size - @config.history_size
- if diff > 0
- if diff <= size
- shift(diff)
- else
- diff -= size
- clear
- val.shift(diff)
+ # If history_size is zero, all histories are dropped.
+ return self if @config.history_size.zero?
+ # If history_size is negative, history size is unlimited.
+ if @config.history_size.positive?
+ diff = size + val.size - @config.history_size
+ if diff > 0
+ if diff <= size
+ shift(diff)
+ else
+ diff -= size
+ clear
+ val.shift(diff)
+ end
end
end
- super(*(val.map{ |v| String.new(v, encoding: Reline.encoding_system_needs) }))
+ super(*(val.map{ |v|
+ String.new(v, encoding: Reline.encoding_system_needs)
+ }))
end
def <<(val)
- shift if size + 1 > @config.history_size
+ # If history_size is zero, all histories are dropped.
+ return self if @config.history_size.zero?
+ # If history_size is negative, history size is unlimited.
+ if @config.history_size.positive?
+ shift if size + 1 > @config.history_size
+ end
super(String.new(val, encoding: Reline.encoding_system_needs))
end
private def check_index(index)
index += size if index < 0
- raise RangeError.new("index=<#{index}>") if index < -@config.history_size or @config.history_size < index
+ if index < -2147483648 or 2147483647 < index
+ raise RangeError.new("integer #{index} too big to convert to `int'")
+ end
+ # If history_size is negative, history size is unlimited.
+ if @config.history_size.positive?
+ if index < -@config.history_size or @config.history_size < index
+ raise RangeError.new("index=<#{index}>")
+ end
+ end
raise IndexError.new("index=<#{index}>") if index < 0 or size <= index
index
end
diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb
index 3886d17f22..1e51d4fa18 100644
--- a/lib/reline/key_actor/emacs.rb
+++ b/lib/reline/key_actor/emacs.rb
@@ -37,9 +37,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 17 ^Q
:ed_quoted_insert,
# 18 ^R
- :ed_search_prev_history,
+ :vi_search_prev,
# 19 ^S
- :ed_search_next_history,
+ :vi_search_next,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
@@ -413,11 +413,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 205 M-M
:ed_unassigned,
# 206 M-N
- :ed_search_next_history,
+ :vi_search_next,
# 207 M-O
:ed_sequence_lead_in,
# 208 M-P
- :ed_search_prev_history,
+ :vi_search_prev,
# 209 M-Q
:ed_unassigned,
# 210 M-R
@@ -477,11 +477,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
# 237 M-m
:ed_unassigned,
# 238 M-n
- :ed_search_next_history,
+ :vi_search_next,
# 239 M-o
:ed_unassigned,
# 240 M-p
- :ed_search_prev_history,
+ :vi_search_prev,
# 241 M-q
:ed_unassigned,
# 242 M-r
diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb
index 865dfa2e00..54b4a60383 100644
--- a/lib/reline/key_actor/vi_command.rb
+++ b/lib/reline/key_actor/vi_command.rb
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 17 ^Q
:ed_ignore,
# 18 ^R
- :ed_search_prev_history,
+ :vi_search_prev,
# 19 ^S
:ed_ignore,
# 20 ^T
@@ -151,7 +151,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
# 74 J
:vi_join_lines,
# 75 K
- :ed_search_prev_history,
+ :vi_search_prev,
# 76 L
:ed_unassigned,
# 77 M
diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb
index 06e94a9c30..b8e89f81d8 100644
--- a/lib/reline/key_actor/vi_insert.rb
+++ b/lib/reline/key_actor/vi_insert.rb
@@ -37,9 +37,9 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
# 17 ^Q
:ed_ignore,
# 18 ^R
- :ed_search_prev_history,
+ :vi_search_prev,
# 19 ^S
- :ed_search_next_history,
+ :vi_search_next,
# 20 ^T
:ed_insert,
# 21 ^U
diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb
index 83136edcf8..017e3db00a 100644
--- a/lib/reline/key_stroke.rb
+++ b/lib/reline/key_stroke.rb
@@ -42,6 +42,8 @@ class Reline::KeyStroke
expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
when Symbol
[rhs] + expand(input.drop(lhs.size))
+ when Array
+ rhs
end
end
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 095a7b5a09..9bdccae9c9 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -2,7 +2,6 @@ require 'reline/kill_ring'
require 'reline/unicode'
require 'tempfile'
-require 'pathname'
class Reline::LineEditor
# TODO: undo
@@ -51,12 +50,6 @@ class Reline::LineEditor
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
MenuInfo = Struct.new('MenuInfo', :target, :list)
- CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
- OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
- NON_PRINTING_START = "\1"
- NON_PRINTING_END = "\2"
- WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
-
def initialize(config, encoding)
@config = config
@completion_append_character = ''
@@ -76,11 +69,35 @@ class Reline::LineEditor
if @prompt_proc
prompt_list = @prompt_proc.(buffer)
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
+ if @config.show_mode_in_prompt
+ if @config.editing_mode_is?(:vi_command)
+ mode_icon = @config.vi_cmd_mode_icon
+ elsif @config.editing_mode_is?(:vi_insert)
+ mode_icon = @config.vi_ins_mode_icon
+ elsif @config.editing_mode_is?(:emacs)
+ mode_icon = @config.emacs_mode_string
+ else
+ mode_icon = '?'
+ end
+ prompt_list.map!{ |pr| mode_icon + pr }
+ end
prompt = prompt_list[@line_index]
prompt_width = calculate_width(prompt, true)
[prompt, prompt_width, prompt_list]
else
prompt_width = calculate_width(prompt, true)
+ if @config.show_mode_in_prompt
+ if @config.editing_mode_is?(:vi_command)
+ mode_icon = @config.vi_cmd_mode_icon
+ elsif @config.editing_mode_is?(:vi_insert)
+ mode_icon = @config.vi_ins_mode_icon
+ elsif @config.editing_mode_is?(:emacs)
+ mode_icon = @config.emacs_mode_string
+ else
+ mode_icon = '?'
+ end
+ prompt = mode_icon + prompt
+ end
[prompt, prompt_width, nil]
end
end
@@ -116,7 +133,7 @@ class Reline::LineEditor
if @line_index.zero?
0
else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
if @prompt_proc
prompt = prompt_list[@line_index]
@@ -190,10 +207,10 @@ class Reline::LineEditor
@is_multiline = false
end
- private def calculate_height_by_lines(lines, prompt_list)
+ private def calculate_height_by_lines(lines, prompt)
result = 0
+ prompt_list = prompt.is_a?(Array) ? prompt : nil
lines.each_with_index { |line, i|
- prompt = ''
prompt = prompt_list[i] if prompt_list and prompt_list[i]
result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
}
@@ -211,40 +228,8 @@ class Reline::LineEditor
width.div(@screen_size.last) + 1
end
- private def split_by_width(prompt, str, max_width)
- lines = [String.new(encoding: @encoding)]
- height = 1
- width = 0
- rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
- in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
- case gc
- when NON_PRINTING_START
- in_zero_width = true
- when NON_PRINTING_END
- in_zero_width = false
- when CSI_REGEXP, OSC_REGEXP
- lines.last << gc
- else
- unless in_zero_width
- mbchar_width = Reline::Unicode.get_mbchar_width(gc)
- if (width += mbchar_width) > max_width
- width = mbchar_width
- lines << nil
- lines << String.new(encoding: @encoding)
- height += 1
- end
- end
- lines.last << gc
- end
- end
- # The cursor moves to next line in first
- if width == max_width
- lines << nil
- lines << String.new(encoding: @encoding)
- height += 1
- end
- [lines, height]
+ private def split_by_width(str, max_width)
+ Reline::Unicode.split_by_width(str, max_width, @encoding)
end
private def scroll_down(val)
@@ -358,7 +343,7 @@ class Reline::LineEditor
new_lines = whole_lines
end
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
- all_height = calculate_height_by_lines(new_lines, prompt_list)
+ all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
diff = all_height - @highest_in_all
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
if diff > 0
@@ -398,7 +383,7 @@ class Reline::LineEditor
if @line_index.zero?
0
else
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
if @prompt_proc
prompt = prompt_list[@line_index]
@@ -457,7 +442,7 @@ class Reline::LineEditor
if @line_index.zero?
0
else
- calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
end
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
move_cursor_down(@first_line_started_from + @started_from)
@@ -488,7 +473,7 @@ class Reline::LineEditor
end
private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
- visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
+ visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
if with_control
if height > @highest_in_this
diff = height - @highest_in_this
@@ -507,8 +492,18 @@ class Reline::LineEditor
Reline::IOGate.move_cursor_column(0)
visual_lines.each_with_index do |line, index|
if line.nil?
- if Reline::IOGate.win? and calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
- # A newline is automatically inserted if a character is rendered at eol on command prompt.
+ if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
+ # reaches the end of line
+ if Reline::IOGate.win?
+ # A newline is automatically inserted if a character is rendered at
+ # eol on command prompt.
+ else
+ # When the cursor is at the end of the line and erases characters
+ # after the cursor, some terminals delete the character at the
+ # cursor position.
+ move_cursor_down(1)
+ Reline::IOGate.move_cursor_column(0)
+ end
else
Reline::IOGate.erase_after_cursor
move_cursor_down(1)
@@ -529,12 +524,14 @@ class Reline::LineEditor
end
Reline::IOGate.erase_after_cursor
if with_control
- move_cursor_up(height - 1)
+ # Just after rendring, so the cursor is on the last line.
if finished?
- move_cursor_down(@started_from)
+ Reline::IOGate.move_cursor_column(0)
+ else
+ # Moves up from bottom of lines to the cursor position.
+ move_cursor_up(height - 1 - @started_from)
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
- move_cursor_down(@started_from)
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
height
end
@@ -543,7 +540,7 @@ class Reline::LineEditor
return before if before.nil? || before.empty?
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
- after.lines("\n", chomp: true)
+ after.lines("\n").map { |l| l.chomp('') }
else
before
end
@@ -1058,29 +1055,7 @@ class Reline::LineEditor
end
private def calculate_width(str, allow_escape_code = false)
- if allow_escape_code
- width = 0
- rest = str.encode(Encoding::UTF_8)
- in_zero_width = false
- rest.scan(WIDTH_SCANNER) do |gc|
- case gc
- when NON_PRINTING_START
- in_zero_width = true
- when NON_PRINTING_END
- in_zero_width = false
- when CSI_REGEXP, OSC_REGEXP
- else
- unless in_zero_width
- width += Reline::Unicode.get_mbchar_width(gc)
- end
- end
- end
- width
- else
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
- w + Reline::Unicode.get_mbchar_width(gc)
- }
- end
+ Reline::Unicode.calculate_width(str, allow_escape_code)
end
private def key_delete(key)
@@ -1258,7 +1233,7 @@ class Reline::LineEditor
if search_word.empty? and Reline.last_incremental_search
search_word = Reline.last_incremental_search
end
- if @history_pointer # TODO
+ if @history_pointer
case prev_search_key
when "\C-r".ord
history_pointer_base = 0
@@ -1330,7 +1305,7 @@ class Reline::LineEditor
end
end
- private def search_history(key)
+ private def incremental_search_history(key)
unless @history_pointer
if @is_multiline
@line_backup_in_history = whole_buffer
@@ -1411,15 +1386,114 @@ class Reline::LineEditor
}
end
- private def ed_search_prev_history(key)
- search_history(key)
+ private def vi_search_prev(key)
+ incremental_search_history(key)
end
- alias_method :reverse_search_history, :ed_search_prev_history
+ alias_method :reverse_search_history, :vi_search_prev
- private def ed_search_next_history(key)
- search_history(key)
+ private def vi_search_next(key)
+ incremental_search_history(key)
end
- alias_method :forward_search_history, :ed_search_next_history
+ alias_method :forward_search_history, :vi_search_next
+
+ private def ed_search_prev_history(key, arg: 1)
+ history = nil
+ h_pointer = nil
+ line_no = nil
+ substr = @line.slice(0, @byte_pointer)
+ if @history_pointer.nil?
+ return if not @line.empty? and substr.empty?
+ history = Reline::HISTORY
+ elsif @history_pointer.zero?
+ history = nil
+ h_pointer = nil
+ else
+ history = Reline::HISTORY.slice(0, @history_pointer)
+ end
+ return if history.nil?
+ if @is_multiline
+ h_pointer = history.rindex { |h|
+ h.split("\n").each_with_index { |l, i|
+ if l.start_with?(substr)
+ line_no = i
+ break
+ end
+ }
+ not line_no.nil?
+ }
+ else
+ h_pointer = history.rindex { |l|
+ l.start_with?(substr)
+ }
+ end
+ return if h_pointer.nil?
+ @history_pointer = h_pointer
+ if @is_multiline
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line_index = line_no
+ @line = @buffer_of_lines.last
+ @rerender_all = true
+ else
+ @line = Reline::HISTORY[@history_pointer]
+ end
+ @cursor_max = calculate_width(@line)
+ arg -= 1
+ ed_search_prev_history(key, arg: arg) if arg > 0
+ end
+ alias_method :history_search_backward, :ed_search_prev_history
+
+ private def ed_search_next_history(key, arg: 1)
+ substr = @line.slice(0, @byte_pointer)
+ if @history_pointer.nil?
+ return
+ elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
+ return
+ end
+ history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
+ h_pointer = nil
+ line_no = nil
+ if @is_multiline
+ h_pointer = history.index { |h|
+ h.split("\n").each_with_index { |l, i|
+ if l.start_with?(substr)
+ line_no = i
+ break
+ end
+ }
+ not line_no.nil?
+ }
+ else
+ h_pointer = history.index { |l|
+ l.start_with?(substr)
+ }
+ end
+ h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
+ return if h_pointer.nil? and not substr.empty?
+ @history_pointer = h_pointer
+ if @is_multiline
+ if @history_pointer.nil? and substr.empty?
+ @buffer_of_lines = []
+ @line_index = 0
+ else
+ @buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
+ @line_index = line_no
+ end
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
+ @line = @buffer_of_lines.last
+ @rerender_all = true
+ else
+ if @history_pointer.nil? and substr.empty?
+ @line = ''
+ else
+ @line = Reline::HISTORY[@history_pointer]
+ end
+ end
+ @cursor_max = calculate_width(@line)
+ arg -= 1
+ ed_search_next_history(key, arg: arg) if arg > 0
+ end
+ alias_method :history_search_forward, :ed_search_next_history
private def ed_prev_history(key, arg: 1)
if @is_multiline and @line_index > 0
@@ -2022,7 +2096,7 @@ class Reline::LineEditor
fp.path
}
system("#{ENV['EDITOR']} #{path}")
- @line = Pathname.new(path).read
+ @line = File.read(path)
finish
end
@@ -2222,7 +2296,6 @@ class Reline::LineEditor
new_pointer = [@byte_pointer, @line_index]
@previous_line_index = @line_index
@byte_pointer, @line_index = @mark_pointer
- @byte_pointer, @line_index = @mark_pointer
@cursor = calculate_width(@line.byteslice(0, @byte_pointer))
@cursor_max = calculate_width(@line)
@mark_pointer = new_pointer
diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec
index 3ced4b8488..1962f61def 100644
--- a/lib/reline/reline.gemspec
+++ b/lib/reline/reline.gemspec
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
spec.summary = %q{Alternative GNU Readline or Editline implementation by pure Ruby.}
spec.description = %q{Alternative GNU Readline or Editline implementation by pure Ruby.}
spec.homepage = 'https://github.com/ruby/reline'
- spec.license = 'Ruby License'
+ spec.license = 'Ruby'
spec.files = Dir['BSDL', 'COPYING', 'README.md', 'lib/**/*']
spec.require_paths = ['lib']
diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb
index 4b30f044f3..cd8c27e85b 100644
--- a/lib/reline/unicode.rb
+++ b/lib/reline/unicode.rb
@@ -35,6 +35,12 @@ class Reline::Unicode
}
EscapedChars = EscapedPairs.keys.map(&:chr)
+ CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
+ OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
+ NON_PRINTING_START = "\1"
+ NON_PRINTING_END = "\2"
+ WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
+
def self.get_mbchar_byte_size_by_first_char(c)
# Checks UTF-8 character byte size
case c.ord
@@ -85,6 +91,68 @@ class Reline::Unicode
end
end
+ def self.calculate_width(str, allow_escape_code = false)
+ if allow_escape_code
+ width = 0
+ rest = str.encode(Encoding::UTF_8)
+ in_zero_width = false
+ rest.scan(WIDTH_SCANNER) do |gc|
+ case gc
+ when NON_PRINTING_START
+ in_zero_width = true
+ when NON_PRINTING_END
+ in_zero_width = false
+ when CSI_REGEXP, OSC_REGEXP
+ else
+ unless in_zero_width
+ width += get_mbchar_width(gc)
+ end
+ end
+ end
+ width
+ else
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
+ w + get_mbchar_width(gc)
+ }
+ end
+ end
+
+ def self.split_by_width(str, max_width, encoding = str.encoding)
+ lines = [String.new(encoding: encoding)]
+ height = 1
+ width = 0
+ rest = str.encode(Encoding::UTF_8)
+ in_zero_width = false
+ rest.scan(WIDTH_SCANNER) do |gc|
+ case gc
+ when NON_PRINTING_START
+ in_zero_width = true
+ when NON_PRINTING_END
+ in_zero_width = false
+ when CSI_REGEXP, OSC_REGEXP
+ lines.last << gc
+ else
+ unless in_zero_width
+ mbchar_width = get_mbchar_width(gc)
+ if (width += mbchar_width) > max_width
+ width = mbchar_width
+ lines << nil
+ lines << String.new(encoding: encoding)
+ height += 1
+ end
+ end
+ lines.last << gc
+ end
+ end
+ # The cursor moves to next line in first
+ if width == max_width
+ lines << nil
+ lines << String.new(encoding: encoding)
+ height += 1
+ end
+ [lines, height]
+ end
+
def self.get_next_mbchar_size(line, byte_pointer)
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
grapheme ? grapheme.bytesize : 0
diff --git a/lib/reline/version.rb b/lib/reline/version.rb
index 1bf544d74b..aa0ef18145 100644
--- a/lib/reline/version.rb
+++ b/lib/reline/version.rb
@@ -1,3 +1,3 @@
module Reline
- VERSION = '0.1.3'
+ VERSION = '0.1.5'
end
diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb
index c229c8536f..2a406e39d3 100644
--- a/lib/reline/windows.rb
+++ b/lib/reline/windows.rb
@@ -92,6 +92,7 @@ class Reline::Windows
@@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
+ @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
@@input_buf = []
@@output_buf = []
@@ -249,9 +250,17 @@ class Reline::Windows
end
def self.clear_screen
- # TODO: Use FillConsoleOutputCharacter and FillConsoleOutputAttribute
- write "\e[2J"
- write "\e[1;1H"
+ csbi = 0.chr * 22
+ return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
+ buffer_width = csbi[0, 2].unpack('S').first
+ attributes = csbi[8, 2].unpack('S').first
+ _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
+ fill_length = buffer_width * (window_bottom - window_top + 1)
+ screen_topleft = window_top * 65536
+ written = 0.chr * 4
+ @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
+ @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
end
def self.set_screen_size(rows, columns)
diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb
index cecb364f89..c1455cd136 100644
--- a/test/reline/test_config.rb
+++ b/test/reline/test_config.rb
@@ -36,6 +36,51 @@ class Reline::Config::Test < Reline::TestCase
assert_equal true, @config.instance_variable_get(:@disable_completion)
end
+ def test_string_value
+ @config.read_lines(<<~LINES.lines)
+ set show-mode-in-prompt on
+ set emacs-mode-string Emacs
+ LINES
+
+ assert_equal 'Emacs', @config.instance_variable_get(:@emacs_mode_string)
+ end
+
+ def test_string_value_with_brackets
+ @config.read_lines(<<~LINES.lines)
+ set show-mode-in-prompt on
+ set emacs-mode-string [Emacs]
+ LINES
+
+ assert_equal '[Emacs]', @config.instance_variable_get(:@emacs_mode_string)
+ end
+
+ def test_string_value_with_brackets_and_quotes
+ @config.read_lines(<<~LINES.lines)
+ set show-mode-in-prompt on
+ set emacs-mode-string "[Emacs]"
+ LINES
+
+ assert_equal '[Emacs]', @config.instance_variable_get(:@emacs_mode_string)
+ end
+
+ def test_string_value_with_parens
+ @config.read_lines(<<~LINES.lines)
+ set show-mode-in-prompt on
+ set emacs-mode-string (Emacs)
+ LINES
+
+ assert_equal '(Emacs)', @config.instance_variable_get(:@emacs_mode_string)
+ end
+
+ def test_string_value_with_parens_and_quotes
+ @config.read_lines(<<~LINES.lines)
+ set show-mode-in-prompt on
+ set emacs-mode-string "(Emacs)"
+ LINES
+
+ assert_equal '(Emacs)', @config.instance_variable_get(:@emacs_mode_string)
+ end
+
def test_comment_line
@config.read_lines([" #a: error\n"])
assert_not_include @config.key_bindings, nil
@@ -213,6 +258,68 @@ class Reline::Config::Test < Reline::TestCase
assert_nothing_raised do
@config.read
end
+ ensure
+ ENV['INPUTRC'] = inputrc_backup
+ end
+
+ def test_inputrc
+ inputrc_backup = ENV['INPUTRC']
+ expected = "#{@tmpdir}/abcde"
+ ENV['INPUTRC'] = expected
+ assert_equal expected, @config.inputrc_path
+ ensure
ENV['INPUTRC'] = inputrc_backup
end
+
+ def test_xdg_config_home
+ home_backup = ENV['HOME']
+ xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
+ xdg_config_home = File.expand_path("#{@tmpdir}/.config/example_dir")
+ expected = File.expand_path("#{xdg_config_home}/readline/inputrc")
+ FileUtils.mkdir_p(File.dirname(expected))
+ FileUtils.touch(expected)
+ ENV['HOME'] = @tmpdir
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home
+ assert_equal expected, @config.inputrc_path
+ ensure
+ FileUtils.rm(expected)
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
+ ENV['HOME'] = home_backup
+ end
+
+ def test_empty_xdg_config_home
+ home_backup = ENV['HOME']
+ xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
+ ENV['HOME'] = @tmpdir
+ ENV['XDG_CONFIG_HOME'] = ''
+ expected = File.expand_path('~/.config/readline/inputrc')
+ FileUtils.mkdir_p(File.dirname(expected))
+ FileUtils.touch(expected)
+ assert_equal expected, @config.inputrc_path
+ ensure
+ FileUtils.rm(expected)
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
+ ENV['HOME'] = home_backup
+ end
+
+ def test_relative_xdg_config_home
+ home_backup = ENV['HOME']
+ xdg_config_home_backup = ENV['XDG_CONFIG_HOME']
+ ENV['HOME'] = @tmpdir
+ expected = File.expand_path('~/.config/readline/inputrc')
+ FileUtils.mkdir_p(File.dirname(expected))
+ FileUtils.touch(expected)
+ result = Dir.chdir(@tmpdir) do
+ xdg_config_home = ".config/example_dir"
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home
+ inputrc = "#{xdg_config_home}/readline/inputrc"
+ FileUtils.mkdir_p(File.dirname(inputrc))
+ FileUtils.touch(inputrc)
+ @config.inputrc_path
+ end
+ assert_equal expected, result
+ FileUtils.rm(expected)
+ ENV['XDG_CONFIG_HOME'] = xdg_config_home_backup
+ ENV['HOME'] = home_backup
+ end
end
diff --git a/test/reline/test_history.rb b/test/reline/test_history.rb
index 189f2db86d..58c240fc96 100644
--- a/test/reline/test_history.rb
+++ b/test/reline/test_history.rb
@@ -242,6 +242,26 @@ class Reline::History::Test < Reline::TestCase
end
end
+ def test_history_size_zero
+ history = history_new(history_size: 0)
+ assert_equal 0, history.size
+ history << 'aa'
+ history << 'bb'
+ assert_equal 0, history.size
+ history.push(*%w{aa bb cc})
+ assert_equal 0, history.size
+ end
+
+ def test_history_size_negative_unlimited
+ history = history_new(history_size: -1)
+ assert_equal 0, history.size
+ history << 'aa'
+ history << 'bb'
+ assert_equal 2, history.size
+ history.push(*%w{aa bb cc})
+ assert_equal 5, history.size
+ end
+
private
def history_new(history_size: 10)
diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb
index c16212c626..a1e4015999 100644
--- a/test/reline/test_key_actor_emacs.rb
+++ b/test/reline/test_key_actor_emacs.rb
@@ -1625,7 +1625,7 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
assert_line('')
end
- def test_ed_search_prev_history
+ def test_vi_search_prev
Reline::HISTORY.concat(%w{abc 123 AAA})
assert_line('')
assert_byte_pointer_size('')
@@ -1897,12 +1897,162 @@ class Reline::KeyActor::Emacs::Test < Reline::TestCase
end
def test_modify_lines_with_wrong_rs
+ verbose, $VERBOSE = $VERBOSE, nil
original_global_slash = $/
$/ = 'b'
+ $VERBOSE = verbose
@line_editor.output_modifier_proc = proc { |output| Reline::Unicode.escape_for_print(output) }
input_keys("abcdef\n")
- assert_equal(['abcdef'], @line_editor.__send__(:modify_lines, @line_editor.whole_lines))
+ result = @line_editor.__send__(:modify_lines, @line_editor.whole_lines)
+ $/ = nil
+ assert_equal(['abcdef'], result)
+ ensure
+ $VERBOSE = nil
$/ = original_global_slash
+ $VERBOSE = verbose
+ end
+
+ def test_ed_search_prev_history
+ Reline::HISTORY.concat([
+ '12356', # old
+ '12aaa',
+ '12345' # new
+ ])
+ input_keys('123')
+ # The ed_search_prev_history doesn't have default binding
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12356')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12356')
+ end
+
+ def test_ed_search_prev_history_with_empty
+ Reline::HISTORY.concat([
+ '12356', # old
+ '12aaa',
+ '12345' # new
+ ])
+ # The ed_search_prev_history doesn't have default binding
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12aaa')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12356')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12356')
+ end
+
+ def test_ed_search_prev_history_without_match
+ Reline::HISTORY.concat([
+ '12356', # old
+ '12aaa',
+ '12345' # new
+ ])
+ input_keys('ABC')
+ # The ed_search_prev_history doesn't have default binding
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('ABC')
+ assert_cursor(3)
+ assert_cursor_max(3)
+ assert_line('ABC')
+ end
+
+ def test_ed_search_next_history
+ Reline::HISTORY.concat([
+ '12356', # old
+ '12aaa',
+ '12345' # new
+ ])
+ input_keys('123')
+ # The ed_search_prev_history and ed_search_next_history doesn't have default binding
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12356')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12356')
+ @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
+ assert_byte_pointer_size('123')
+ assert_cursor(3)
+ assert_cursor_max(5)
+ assert_line('12345')
+ end
+
+ def test_ed_search_next_history_with_empty
+ Reline::HISTORY.concat([
+ '12356', # old
+ '12aaa',
+ '12345' # new
+ ])
+ # The ed_search_prev_history and ed_search_next_history doesn't have default binding
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12aaa')
+ @line_editor.__send__(:ed_search_prev_history, "\C-p".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12356')
+ @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12aaa')
+ @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(5)
+ assert_line('12345')
+ @line_editor.__send__(:ed_search_next_history, "\C-n".ord)
+ assert_byte_pointer_size('')
+ assert_cursor(0)
+ assert_cursor_max(0)
+ assert_line('')
end
=begin # TODO: move KeyStroke instance from Reline to LineEditor
diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb
index 224e93d9a0..15675a9b5a 100644
--- a/test/reline/test_key_stroke.rb
+++ b/test/reline/test_key_stroke.rb
@@ -16,10 +16,10 @@ class Reline::KeyStroke::Test < Reline::TestCase
def test_match_status
config = Reline::Config.new
{
- "a" => "xx",
- "ab" => "y",
- "abc" => "z",
- "x" => "rr"
+ 'a' => 'xx',
+ 'ab' => 'y',
+ 'abc' => 'z',
+ 'x' => 'rr'
}.each_pair do |key, func|
config.add_default_key_binding(key.bytes, func.bytes)
end
@@ -35,4 +35,15 @@ class Reline::KeyStroke::Test < Reline::TestCase
assert_equal(:unmatched, stroke.match_status("m".bytes))
assert_equal(:matched, stroke.match_status("abzwabk".bytes))
end
+
+ def test_aaa
+ config = Reline::Config.new
+ {
+ 'abc' => '123',
+ }.each_pair do |key, func|
+ config.add_default_key_binding(key.bytes, func.bytes)
+ end
+ stroke = Reline::KeyStroke.new(config)
+ assert_equal('123'.bytes, stroke.expand('abc'.bytes))
+ end
end
diff --git a/test/reline/test_reline.rb b/test/reline/test_reline.rb
index 0de2462a08..d2de4690d5 100644
--- a/test/reline/test_reline.rb
+++ b/test/reline/test_reline.rb
@@ -7,6 +7,12 @@ class Reline::Test < Reline::TestCase
end
def setup
+ Reline.output_modifier_proc = nil
+ Reline.completion_proc = nil
+ Reline.prompt_proc = nil
+ Reline.auto_indent_proc = nil
+ Reline.pre_input_hook = nil
+ Reline.dig_perfect_match_proc = nil
end
def teardown
@@ -14,6 +20,8 @@ class Reline::Test < Reline::TestCase
end
def test_completion_append_character
+ completion_append_character = Reline.completion_append_character
+
assert_equal(nil, Reline.completion_append_character)
Reline.completion_append_character = ""
@@ -33,57 +41,85 @@ class Reline::Test < Reline::TestCase
Reline.completion_append_character = nil
assert_equal(nil, Reline.completion_append_character)
+ ensure
+ Reline.completion_append_character = completion_append_character
end
def test_basic_word_break_characters
+ basic_word_break_characters = Reline.basic_word_break_characters
+
assert_equal(" \t\n`><=;|&{(", Reline.basic_word_break_characters)
Reline.basic_word_break_characters = "[".encode(Encoding::ASCII)
assert_equal("[", Reline.basic_word_break_characters)
assert_equal(get_reline_encoding, Reline.basic_word_break_characters.encoding)
+ ensure
+ Reline.basic_word_break_characters = basic_word_break_characters
end
def test_completer_word_break_characters
+ completer_word_break_characters = Reline.completer_word_break_characters
+
assert_equal(" \t\n`><=;|&{(", Reline.completer_word_break_characters)
Reline.completer_word_break_characters = "[".encode(Encoding::ASCII)
assert_equal("[", Reline.completer_word_break_characters)
assert_equal(get_reline_encoding, Reline.completer_word_break_characters.encoding)
+ ensure
+ Reline.completer_word_break_characters = completer_word_break_characters
end
def test_basic_quote_characters
+ basic_quote_characters = Reline.basic_quote_characters
+
assert_equal('"\'', Reline.basic_quote_characters)
Reline.basic_quote_characters = "`".encode(Encoding::ASCII)
assert_equal("`", Reline.basic_quote_characters)
assert_equal(get_reline_encoding, Reline.basic_quote_characters.encoding)
+ ensure
+ Reline.basic_quote_characters = basic_quote_characters
end
def test_completer_quote_characters
+ completer_quote_characters = Reline.completer_quote_characters
+
assert_equal('"\'', Reline.completer_quote_characters)
Reline.completer_quote_characters = "`".encode(Encoding::ASCII)
assert_equal("`", Reline.completer_quote_characters)
assert_equal(get_reline_encoding, Reline.completer_quote_characters.encoding)
+ ensure
+ Reline.completer_quote_characters = completer_quote_characters
end
def test_filename_quote_characters
+ filename_quote_characters = Reline.filename_quote_characters
+
assert_equal('', Reline.filename_quote_characters)
Reline.filename_quote_characters = "\'".encode(Encoding::ASCII)
assert_equal("\'", Reline.filename_quote_characters)
assert_equal(get_reline_encoding, Reline.filename_quote_characters.encoding)
+ ensure
+ Reline.filename_quote_characters = filename_quote_characters
end
def test_special_prefixes
+ special_prefixes = Reline.special_prefixes
+
assert_equal('', Reline.special_prefixes)
Reline.special_prefixes = "\'".encode(Encoding::ASCII)
assert_equal("\'", Reline.special_prefixes)
assert_equal(get_reline_encoding, Reline.special_prefixes.encoding)
+ ensure
+ Reline.special_prefixes = special_prefixes
end
def test_completion_case_fold
+ completion_case_fold = Reline.completion_case_fold
+
assert_equal(nil, Reline.completion_case_fold)
Reline.completion_case_fold = true
@@ -91,6 +127,8 @@ class Reline::Test < Reline::TestCase
Reline.completion_case_fold = "hoge".encode(Encoding::ASCII)
assert_equal("hoge", Reline.completion_case_fold)
+ ensure
+ Reline.completion_case_fold = completion_case_fold
end
def test_completion_proc
diff --git a/test/reline/test_within_pipe.rb b/test/reline/test_within_pipe.rb
index 46b4465f32..53989a794f 100644
--- a/test/reline/test_within_pipe.rb
+++ b/test/reline/test_within_pipe.rb
@@ -3,9 +3,10 @@ require_relative 'helper'
class Reline::WithinPipeTest < Reline::TestCase
def setup
Reline.send(:test_mode)
- @reader, @writer = IO.pipe((RELINE_TEST_ENCODING rescue Encoding.default_external))
- Reline.input = @reader
- @output = Reline.output = File.open(IO::NULL, 'w')
+ @input_reader, @writer = IO.pipe((RELINE_TEST_ENCODING rescue Encoding.default_external))
+ Reline.input = @input_reader
+ @reader, @output_writer = IO.pipe((RELINE_TEST_ENCODING rescue Encoding.default_external))
+ @output = Reline.output = @output_writer
@config = Reline.send(:core).config
@line_editor = Reline.send(:core).line_editor
end
@@ -14,9 +15,11 @@ class Reline::WithinPipeTest < Reline::TestCase
Reline.input = STDIN
Reline.output = STDOUT
Reline.point = 0
- @reader.close
+ Reline.delete_text
+ @input_reader.close
@writer.close
- @output.close
+ @reader.close
+ @output_writer.close
@config.reset
end
diff --git a/test/reline/yamatanooroti/test_rendering.rb b/test/reline/yamatanooroti/test_rendering.rb
index 4eab6661d6..0ab43fa60c 100644
--- a/test/reline/yamatanooroti/test_rendering.rb
+++ b/test/reline/yamatanooroti/test_rendering.rb
@@ -5,14 +5,29 @@ begin
class Reline::TestRendering < Yamatanooroti::TestCase
def setup
- inputrc_backup = ENV['INPUTRC']
- ENV['INPUTRC'] = 'nonexistent_file'
- start_terminal(5, 30, %w{ruby -Ilib bin/multiline_repl})
- sleep 0.5
- ENV['INPUTRC'] = inputrc_backup
+ @pwd = Dir.pwd
+ @tmpdir = File.join(File.expand_path(Dir.tmpdir), "test_reline_config_#{$$}")
+ begin
+ Dir.mkdir(@tmpdir)
+ rescue Errno::EEXIST
+ FileUtils.rm_rf(@tmpdir)
+ Dir.mkdir(@tmpdir)
+ end
+ Dir.chdir(@tmpdir)
+ @inputrc_backup = ENV['INPUTRC']
+ @inputrc_file = ENV['INPUTRC'] = File.join(@tmpdir, 'temporaty_inputrc')
+ File.unlink(@inputrc_file) if File.exist?(@inputrc_file)
+ end
+
+ def teardown
+ Dir.chdir(@pwd)
+ FileUtils.rm_rf(@tmpdir)
+ ENV['INPUTRC'] = @inputrc_backup
end
def test_history_back
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
write(":a\n")
write("\C-p")
close
@@ -25,6 +40,8 @@ begin
end
def test_backspace
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
write(":abc\C-h\n")
close
assert_screen(<<~EOC)
@@ -34,6 +51,172 @@ begin
prompt>
EOC
end
+
+ def test_autowrap
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write('01234567890123456789012')
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> 0123456789012345678901
+ 2
+ EOC
+ end
+
+ def test_finish_autowrapped_line
+ start_terminal(10, 40, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write("[{'user'=>{'email'=>'a@a', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]\n")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> [{'user'=>{'email'=>'a@a', 'id'=
+ >'ABC'}, 'version'=>4, 'status'=>'succee
+ ded'}]
+ => [{"user"=>{"email"=>"a@a", "id"=>"ABC
+ "}, "version"=>4, "status"=>"succeeded"}
+ ]
+ prompt>
+ EOC
+ end
+
+ def test_finish_autowrapped_line_in_the_middle_of_lines
+ start_terminal(20, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write("[{'user'=>{'email'=>'abcdef@abcdef', 'id'=>'ABC'}, 'version'=>4, 'status'=>'succeeded'}]#{"\C-b"*7}\n")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> [{'user'=>{'email'=>'a
+ bcdef@abcdef', 'id'=>'ABC'}, '
+ version'=>4, 'status'=>'succee
+ ded'}]
+ => [{"user"=>{"email"=>"abcdef
+ @abcdef", "id"=>"ABC"}, "versi
+ on"=>4, "status"=>"succeeded"}
+ ]
+ prompt>
+ EOC
+ end
+
+ def test_finish_autowrapped_line_in_the_middle_of_multilines
+ start_terminal(30, 16, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write("<<~EOM\n ABCDEFG\nEOM\n")
+ close
+ assert_screen(<<~'EOC')
+ Multiline REPL.
+ prompt> <<~EOM
+ prompt> ABCDEF
+ G
+ prompt> EOM
+ => "ABCDEFG\n"
+ prompt>
+ EOC
+ end
+
+ def test_prompt
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~'LINES'
+ "abc": "123"
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write("abc\n")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ prompt> 123
+ => 123
+ prompt>
+ EOC
+ end
+
+ def test_mode_icon_emacs
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~LINES
+ set show-mode-in-prompt on
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ @prompt>
+ EOC
+ end
+
+ def test_mode_icon_vi
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~LINES
+ set editing-mode vi
+ set show-mode-in-prompt on
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ sleep 0.5
+ write(":a\n\C-[k")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ (ins)prompt> :a
+ => :a
+ (cmd)prompt> :a
+ EOC
+ end
+
+ def test_original_mode_icon_emacs
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~LINES
+ set show-mode-in-prompt on
+ set emacs-mode-string [emacs]
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ [emacs]prompt>
+ EOC
+ end
+
+ def test_original_mode_icon_with_quote
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~LINES
+ set show-mode-in-prompt on
+ set emacs-mode-string "[emacs]"
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ [emacs]prompt>
+ EOC
+ end
+
+ def test_original_mode_icon_vi
+ File.open(@inputrc_file, 'w') do |f|
+ f.write <<~LINES
+ set editing-mode vi
+ set show-mode-in-prompt on
+ set vi-ins-mode-string "{InS}"
+ set vi-cmd-mode-string "{CmD}"
+ LINES
+ end
+ start_terminal(5, 30, %W{ruby -I#{@pwd}/lib #{@pwd}/bin/multiline_repl})
+ write(":a\n\C-[k")
+ close
+ assert_screen(<<~EOC)
+ Multiline REPL.
+ {InS}prompt> :a
+ => :a
+ {CmD}prompt> :a
+ EOC
+ end
end
rescue LoadError, NameError
# On Ruby repository, this test suit doesn't run because Ruby repo doesn't