[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:
parent
59ab002665
commit
f567633a16
@ -367,89 +367,39 @@ module Reline
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
|
# GNU Readline watis for "keyseq-timeout" milliseconds when the input is
|
||||||
# is followed by a character, and times out and treats it as a standalone
|
# ambiguous whether it is matching or matched.
|
||||||
# ESC if the second character does not arrive. If the second character
|
# If the next character does not arrive within the specified timeout, input
|
||||||
# comes before timed out, it is treated as a modifier key with the
|
# is considered as matched.
|
||||||
# meta-property of meta-key, so that it can be distinguished from
|
# `ESC` is ambiguous because it can be a standalone ESC (matched) or part of
|
||||||
# multibyte characters with the 8th bit turned on.
|
# `ESC char` or part of CSI sequence (matching).
|
||||||
#
|
|
||||||
# GNU Readline will wait for the 2nd character with "keyseq-timeout"
|
|
||||||
# milli-seconds but wait forever after 3rd characters.
|
|
||||||
private def read_io(keyseq_timeout, &block)
|
private def read_io(keyseq_timeout, &block)
|
||||||
buffer = []
|
buffer = []
|
||||||
|
status = KeyStroke::MATCHING
|
||||||
loop do
|
loop do
|
||||||
c = io_gate.getc(Float::INFINITY)
|
timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY
|
||||||
if c == -1
|
c = io_gate.getc(timeout)
|
||||||
result = :unmatched
|
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
|
else
|
||||||
buffer << c
|
buffer << c
|
||||||
result = key_stroke.match_status(buffer)
|
status = 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)
|
|
||||||
else
|
|
||||||
expanded, rest_bytes = key_stroke.expand(buffer)
|
|
||||||
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
|
|
||||||
block.(expanded)
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
|
if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED
|
||||||
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)
|
expanded, rest_bytes = key_stroke.expand(buffer)
|
||||||
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
|
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
|
||||||
block.(expanded)
|
block.call(expanded)
|
||||||
return :break
|
return
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -7,17 +7,35 @@ class Reline::KeyStroke
|
|||||||
@config = config
|
@config = config
|
||||||
end
|
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)
|
def match_status(input)
|
||||||
if key_mapping.matching?(input)
|
matching = key_mapping.matching?(input)
|
||||||
:matching
|
matched = key_mapping.get(input)
|
||||||
elsif key_mapping.get(input)
|
|
||||||
:matched
|
# 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
|
elsif input[0] == ESC_BYTE
|
||||||
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
|
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
|
||||||
elsif input.size == 1
|
elsif input.size == 1
|
||||||
:matched
|
MATCHED
|
||||||
else
|
else
|
||||||
:unmatched
|
UNMATCHED
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -25,7 +43,8 @@ class Reline::KeyStroke
|
|||||||
matched_bytes = nil
|
matched_bytes = nil
|
||||||
(1..input.size).each do |i|
|
(1..input.size).each do |i|
|
||||||
bytes = input.take(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
|
end
|
||||||
return [[], []] unless matched_bytes
|
return [[], []] unless matched_bytes
|
||||||
|
|
||||||
@ -50,13 +69,17 @@ class Reline::KeyStroke
|
|||||||
# returns match status of CSI/SS3 sequence and matched length
|
# returns match status of CSI/SS3 sequence and matched length
|
||||||
def match_unknown_escape_sequence(input, vi_mode: false)
|
def match_unknown_escape_sequence(input, vi_mode: false)
|
||||||
idx = 0
|
idx = 0
|
||||||
return :unmatched unless input[idx] == ESC_BYTE
|
return UNMATCHED unless input[idx] == ESC_BYTE
|
||||||
idx += 1
|
idx += 1
|
||||||
idx += 1 if input[idx] == ESC_BYTE
|
idx += 1 if input[idx] == ESC_BYTE
|
||||||
|
|
||||||
case input[idx]
|
case input[idx]
|
||||||
when nil
|
when nil
|
||||||
return :matching
|
if idx == 1 # `ESC`
|
||||||
|
return MATCHING_MATCHED
|
||||||
|
else # `ESC ESC`
|
||||||
|
return MATCHING
|
||||||
|
end
|
||||||
when 91 # == '['.ord
|
when 91 # == '['.ord
|
||||||
# CSI sequence `ESC [ ... char`
|
# CSI sequence `ESC [ ... char`
|
||||||
idx += 1
|
idx += 1
|
||||||
@ -67,9 +90,17 @@ class Reline::KeyStroke
|
|||||||
idx += 1
|
idx += 1
|
||||||
else
|
else
|
||||||
# `ESC char` or `ESC ESC char`
|
# `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
|
end
|
||||||
input[idx + 1] ? :unmatched : input[idx] ? :matched : :matching
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def key_mapping
|
def key_mapping
|
||||||
|
@ -1081,17 +1081,7 @@ class Reline::LineEditor
|
|||||||
else # single byte
|
else # single byte
|
||||||
return if key.char >= 128 # maybe, first byte of multi byte
|
return if key.char >= 128 # maybe, first byte of multi byte
|
||||||
method_symbol = @config.editing_mode.get_method(key.combined_char)
|
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)
|
process_key(key.combined_char, method_symbol)
|
||||||
end
|
|
||||||
@multibyte_buffer.clear
|
@multibyte_buffer.clear
|
||||||
end
|
end
|
||||||
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
|
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)
|
config.add_default_key_binding(key.bytes, func.bytes)
|
||||||
end
|
end
|
||||||
stroke = Reline::KeyStroke.new(config)
|
stroke = Reline::KeyStroke.new(config)
|
||||||
assert_equal(:matching, stroke.match_status("a".bytes))
|
assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".bytes))
|
||||||
assert_equal(:matching, stroke.match_status("ab".bytes))
|
assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("ab".bytes))
|
||||||
assert_equal(:matched, stroke.match_status("abc".bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status("abz".bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abz".bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status("abcx".bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abcx".bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status("aa".bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("aa".bytes))
|
||||||
assert_equal(:matched, stroke.match_status("x".bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("x".bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status("xa".bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("xa".bytes))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_match_unknown
|
def test_match_unknown
|
||||||
@ -50,10 +50,10 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
"\e\eX"
|
"\e\eX"
|
||||||
]
|
]
|
||||||
sequences.each do |seq|
|
sequences.each do |seq|
|
||||||
assert_equal(:matched, stroke.match_status(seq.bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(seq.bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status(seq.bytes + [32]))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(seq.bytes + [32]))
|
||||||
(1...seq.size).each do |i|
|
(2...seq.size).each do |i|
|
||||||
assert_equal(:matching, stroke.match_status(seq.bytes.take(i)))
|
assert_equal(Reline::KeyStroke::MATCHING, stroke.match_status(seq.bytes.take(i)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -84,8 +84,8 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
config.add_default_key_binding(key.bytes, func.bytes)
|
config.add_default_key_binding(key.bytes, func.bytes)
|
||||||
end
|
end
|
||||||
stroke = Reline::KeyStroke.new(config)
|
stroke = Reline::KeyStroke.new(config)
|
||||||
assert_equal(:unmatched, stroke.match_status('zzz'.bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
|
||||||
assert_equal(:matched, stroke.match_status('abc'.bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes))
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_with_reline_key
|
def test_with_reline_key
|
||||||
@ -97,9 +97,9 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
config.add_oneshot_key_binding(key, func.bytes)
|
config.add_oneshot_key_binding(key, func.bytes)
|
||||||
end
|
end
|
||||||
stroke = Reline::KeyStroke.new(config)
|
stroke = Reline::KeyStroke.new(config)
|
||||||
assert_equal(:unmatched, stroke.match_status('da'.bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
|
||||||
assert_equal(:matched, stroke.match_status("\eda".bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".bytes))
|
||||||
assert_equal(:unmatched, stroke.match_status([32, 195, 164]))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status([32, 195, 164]))
|
||||||
assert_equal(:matched, stroke.match_status([195, 164]))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164]))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user