зеркало из https://github.com/github/ruby.git
[ruby/reline] Refactor input key reading
(https://github.com/ruby/reline/pull/712) * Add key binding matching status :matching_matched * Simplify read_2nd_character * Add a comment of matching status and EOF * Matching status to a constant * Expand complicated ternary operators to case-when https://github.com/ruby/reline/commit/64deec100b
This commit is contained in:
Родитель
59ab002665
Коммит
f567633a16
|
@ -367,92 +367,42 @@ 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
|
||||
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
|
||||
status = KeyStroke::UNMATCHED
|
||||
end
|
||||
else
|
||||
buffer << c
|
||||
result = key_stroke.match_status(buffer)
|
||||
status = key_stroke.match_status(buffer)
|
||||
end
|
||||
case result
|
||||
when :matched
|
||||
|
||||
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)
|
||||
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)
|
||||
else
|
||||
expanded, rest_bytes = key_stroke.expand(buffer)
|
||||
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
|
||||
block.(expanded)
|
||||
end
|
||||
break
|
||||
block.call(expanded)
|
||||
return
|
||||
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
|
||||
expanded, rest_bytes = key_stroke.expand(buffer)
|
||||
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
|
||||
block.(expanded)
|
||||
return :break
|
||||
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
|
||||
|
||||
def ambiguous_width
|
||||
may_req_ambiguous_char_width unless defined? @ambiguous_width
|
||||
@ambiguous_width
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче