summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/reline.rb96
-rw-r--r--lib/reline/key_stroke.rb53
-rw-r--r--lib/reline/line_editor.rb12
-rw-r--r--test/reline/test_key_stroke.rb36
4 files changed, 84 insertions, 113 deletions
diff --git a/lib/reline.rb b/lib/reline.rb
index 6bae469894..720df286a1 100644
--- a/lib/reline.rb
+++ b/lib/reline.rb
@@ -367,89 +367,39 @@ module Reline
end
end
- # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
- # is followed by a character, and times out and treats it as a standalone
- # ESC if the second character does not arrive. If the second character
- # comes before timed out, it is treated as a modifier key with the
- # meta-property of meta-key, so that it can be distinguished from
- # multibyte characters with the 8th bit turned on.
- #
- # GNU Readline will wait for the 2nd character with "keyseq-timeout"
- # milli-seconds but wait forever after 3rd characters.
+ # GNU Readline watis for "keyseq-timeout" milliseconds when the input is
+ # ambiguous whether it is matching or matched.
+ # If the next character does not arrive within the specified timeout, input
+ # is considered as matched.
+ # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of
+ # `ESC char` or part of CSI sequence (matching).
private def read_io(keyseq_timeout, &block)
buffer = []
+ status = KeyStroke::MATCHING
loop do
- c = io_gate.getc(Float::INFINITY)
- if c == -1
- result = :unmatched
- else
- buffer << c
- result = key_stroke.match_status(buffer)
- end
- case result
- when :matched
- expanded, rest_bytes = key_stroke.expand(buffer)
- rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
- block.(expanded)
- break
- when :matching
- if buffer.size == 1
- case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
- when :break then break
- when :next then next
- end
- end
- when :unmatched
- if buffer.size == 1 and c == "\e".ord
- read_escaped_key(keyseq_timeout, c, block)
+ timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY
+ c = io_gate.getc(timeout)
+ if c.nil? || c == -1
+ if status == KeyStroke::MATCHING_MATCHED
+ status = KeyStroke::MATCHED
+ elsif buffer.empty?
+ # io_gate is closed and reached EOF
+ block.call([Key.new(nil, nil, false)])
+ return
else
- expanded, rest_bytes = key_stroke.expand(buffer)
- rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
- block.(expanded)
+ status = KeyStroke::UNMATCHED
end
- break
+ else
+ buffer << c
+ status = key_stroke.match_status(buffer)
end
- end
- end
- private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
- succ_c = io_gate.getc(keyseq_timeout.fdiv(1000))
- if succ_c
- case key_stroke.match_status(buffer.dup.push(succ_c))
- when :unmatched
- if c == "\e".ord
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
- else
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
- end
- return :break
- when :matching
- io_gate.ungetc(succ_c)
- return :next
- when :matched
- buffer << succ_c
+ if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED
expanded, rest_bytes = key_stroke.expand(buffer)
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
- block.(expanded)
- return :break
+ block.call(expanded)
+ return
end
- else
- block.([Reline::Key.new(c, c, false)])
- return :break
- end
- end
-
- private def read_escaped_key(keyseq_timeout, c, block)
- escaped_c = io_gate.getc(keyseq_timeout.fdiv(1000))
-
- if escaped_c.nil?
- block.([Reline::Key.new(c, c, false)])
- elsif escaped_c >= 128 # maybe, first byte of multi byte
- block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
- elsif escaped_c == "\e".ord # escape twice
- block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
- else
- block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
end
end
diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb
index 419ddd8cea..ba40899685 100644
--- a/lib/reline/key_stroke.rb
+++ b/lib/reline/key_stroke.rb
@@ -7,17 +7,35 @@ class Reline::KeyStroke
@config = config
end
+ # Input exactly matches to a key sequence
+ MATCHING = :matching
+ # Input partially matches to a key sequence
+ MATCHED = :matched
+ # Input matches to a key sequence and the key sequence is a prefix of another key sequence
+ MATCHING_MATCHED = :matching_matched
+ # Input does not match to any key sequence
+ UNMATCHED = :unmatched
+
def match_status(input)
- if key_mapping.matching?(input)
- :matching
- elsif key_mapping.get(input)
- :matched
+ matching = key_mapping.matching?(input)
+ matched = key_mapping.get(input)
+
+ # FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
+ matched ||= input.size == 1
+ matching ||= input == [ESC_BYTE]
+
+ if matching && matched
+ MATCHING_MATCHED
+ elsif matching
+ MATCHING
+ elsif matched
+ MATCHED
elsif input[0] == ESC_BYTE
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
elsif input.size == 1
- :matched
+ MATCHED
else
- :unmatched
+ UNMATCHED
end
end
@@ -25,7 +43,8 @@ class Reline::KeyStroke
matched_bytes = nil
(1..input.size).each do |i|
bytes = input.take(i)
- matched_bytes = bytes if match_status(bytes) != :unmatched
+ status = match_status(bytes)
+ matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
end
return [[], []] unless matched_bytes
@@ -50,13 +69,17 @@ class Reline::KeyStroke
# returns match status of CSI/SS3 sequence and matched length
def match_unknown_escape_sequence(input, vi_mode: false)
idx = 0
- return :unmatched unless input[idx] == ESC_BYTE
+ return UNMATCHED unless input[idx] == ESC_BYTE
idx += 1
idx += 1 if input[idx] == ESC_BYTE
case input[idx]
when nil
- return :matching
+ if idx == 1 # `ESC`
+ return MATCHING_MATCHED
+ else # `ESC ESC`
+ return MATCHING
+ end
when 91 # == '['.ord
# CSI sequence `ESC [ ... char`
idx += 1
@@ -67,9 +90,17 @@ class Reline::KeyStroke
idx += 1
else
# `ESC char` or `ESC ESC char`
- return :unmatched if vi_mode
+ return UNMATCHED if vi_mode
+ end
+
+ case input.size
+ when idx
+ MATCHING
+ when idx + 1
+ MATCHED
+ else
+ UNMATCHED
end
- input[idx + 1] ? :unmatched : input[idx] ? :matched : :matching
end
def key_mapping
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index faa5b0194e..884b78ffdf 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -1081,17 +1081,7 @@ class Reline::LineEditor
else # single byte
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
- if key.with_meta and method_symbol == :ed_unassigned
- if @config.editing_mode_is?(:vi_command, :vi_insert)
- # split ESC + key in vi mode
- method_symbol = @config.editing_mode.get_method("\e".ord)
- process_key("\e".ord, method_symbol)
- method_symbol = @config.editing_mode.get_method(key.char)
- process_key(key.char, method_symbol)
- end
- else
- process_key(key.combined_char, method_symbol)
- end
+ process_key(key.combined_char, method_symbol)
@multibyte_buffer.clear
end
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb
index de67c7ad44..ec70a05957 100644
--- a/test/reline/test_key_stroke.rb
+++ b/test/reline/test_key_stroke.rb
@@ -24,14 +24,14 @@ class Reline::KeyStroke::Test < Reline::TestCase
config.add_default_key_binding(key.bytes, func.bytes)
end
stroke = Reline::KeyStroke.new(config)
- assert_equal(:matching, stroke.match_status("a".bytes))
- assert_equal(:matching, stroke.match_status("ab".bytes))
- assert_equal(:matched, stroke.match_status("abc".bytes))
- assert_equal(:unmatched, stroke.match_status("abz".bytes))
- assert_equal(:unmatched, stroke.match_status("abcx".bytes))
- assert_equal(:unmatched, stroke.match_status("aa".bytes))
- assert_equal(:matched, stroke.match_status("x".bytes))
- assert_equal(:unmatched, stroke.match_status("xa".bytes))
+ assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".bytes))
+ assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("ab".bytes))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abz".bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abcx".bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("aa".bytes))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("x".bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("xa".bytes))
end
def test_match_unknown
@@ -50,10 +50,10 @@ class Reline::KeyStroke::Test < Reline::TestCase
"\e\eX"
]
sequences.each do |seq|
- assert_equal(:matched, stroke.match_status(seq.bytes))
- assert_equal(:unmatched, stroke.match_status(seq.bytes + [32]))
- (1...seq.size).each do |i|
- assert_equal(:matching, stroke.match_status(seq.bytes.take(i)))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(seq.bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(seq.bytes + [32]))
+ (2...seq.size).each do |i|
+ assert_equal(Reline::KeyStroke::MATCHING, stroke.match_status(seq.bytes.take(i)))
end
end
end
@@ -84,8 +84,8 @@ class Reline::KeyStroke::Test < Reline::TestCase
config.add_default_key_binding(key.bytes, func.bytes)
end
stroke = Reline::KeyStroke.new(config)
- assert_equal(:unmatched, stroke.match_status('zzz'.bytes))
- assert_equal(:matched, stroke.match_status('abc'.bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes))
end
def test_with_reline_key
@@ -97,9 +97,9 @@ class Reline::KeyStroke::Test < Reline::TestCase
config.add_oneshot_key_binding(key, func.bytes)
end
stroke = Reline::KeyStroke.new(config)
- assert_equal(:unmatched, stroke.match_status('da'.bytes))
- assert_equal(:matched, stroke.match_status("\eda".bytes))
- assert_equal(:unmatched, stroke.match_status([32, 195, 164]))
- assert_equal(:matched, stroke.match_status([195, 164]))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".bytes))
+ assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status([32, 195, 164]))
+ assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164]))
end
end