[ruby/reline] KeyStroke handles multibyte character
(https://github.com/ruby/reline/pull/713) https://github.com/ruby/reline/commit/5a8da85f2b
This commit is contained in:
parent
def684508c
commit
c1dcd1d496
@ -307,6 +307,7 @@ module Reline
|
|||||||
otio = io_gate.prep
|
otio = io_gate.prep
|
||||||
|
|
||||||
may_req_ambiguous_char_width
|
may_req_ambiguous_char_width
|
||||||
|
key_stroke.encoding = encoding
|
||||||
line_editor.reset(prompt)
|
line_editor.reset(prompt)
|
||||||
if multiline
|
if multiline
|
||||||
line_editor.multiline_on
|
line_editor.multiline_on
|
||||||
@ -485,7 +486,7 @@ module Reline
|
|||||||
def self.core
|
def self.core
|
||||||
@core ||= Core.new { |core|
|
@core ||= Core.new { |core|
|
||||||
core.config = Reline::Config.new
|
core.config = Reline::Config.new
|
||||||
core.key_stroke = Reline::KeyStroke.new(core.config)
|
core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
|
||||||
core.line_editor = Reline::LineEditor.new(core.config)
|
core.line_editor = Reline::LineEditor.new(core.config)
|
||||||
|
|
||||||
core.basic_word_break_characters = " \t\n`><=;|&{("
|
core.basic_word_break_characters = " \t\n`><=;|&{("
|
||||||
|
@ -3,8 +3,11 @@ class Reline::KeyStroke
|
|||||||
CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
|
CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
|
||||||
CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
|
CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
|
||||||
|
|
||||||
def initialize(config)
|
attr_accessor :encoding
|
||||||
|
|
||||||
|
def initialize(config, encoding)
|
||||||
@config = config
|
@config = config
|
||||||
|
@encoding = encoding
|
||||||
end
|
end
|
||||||
|
|
||||||
# Input exactly matches to a key sequence
|
# Input exactly matches to a key sequence
|
||||||
@ -21,7 +24,7 @@ class Reline::KeyStroke
|
|||||||
matched = key_mapping.get(input)
|
matched = key_mapping.get(input)
|
||||||
|
|
||||||
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
|
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
|
||||||
matched ||= input.size == 1
|
matched ||= input.size == 1 && input[0] < 0x80
|
||||||
matching ||= input == [ESC_BYTE]
|
matching ||= input == [ESC_BYTE]
|
||||||
|
|
||||||
if matching && matched
|
if matching && matched
|
||||||
@ -32,10 +35,14 @@ class Reline::KeyStroke
|
|||||||
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
|
|
||||||
MATCHED
|
|
||||||
else
|
else
|
||||||
UNMATCHED
|
s = input.pack('c*').force_encoding(@encoding)
|
||||||
|
if s.valid_encoding?
|
||||||
|
s.size == 1 ? MATCHED : UNMATCHED
|
||||||
|
else
|
||||||
|
# Invalid string is MATCHING (part of valid string) or MATCHED (invalid bytes to be ignored)
|
||||||
|
MATCHING_MATCHED
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -45,6 +52,7 @@ class Reline::KeyStroke
|
|||||||
bytes = input.take(i)
|
bytes = input.take(i)
|
||||||
status = match_status(bytes)
|
status = match_status(bytes)
|
||||||
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
|
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
|
||||||
|
break if status == MATCHED || status == UNMATCHED
|
||||||
end
|
end
|
||||||
return [[], []] unless matched_bytes
|
return [[], []] unless matched_bytes
|
||||||
|
|
||||||
@ -53,12 +61,15 @@ class Reline::KeyStroke
|
|||||||
keys = func.map { |c| Reline::Key.new(c, c, false) }
|
keys = func.map { |c| Reline::Key.new(c, c, false) }
|
||||||
elsif func
|
elsif func
|
||||||
keys = [Reline::Key.new(func, func, false)]
|
keys = [Reline::Key.new(func, func, false)]
|
||||||
elsif matched_bytes.size == 1
|
|
||||||
keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
|
|
||||||
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
|
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
|
||||||
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
|
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
|
||||||
else
|
else
|
||||||
keys = []
|
s = matched_bytes.pack('c*').force_encoding(@encoding)
|
||||||
|
if s.valid_encoding? && s.size == 1
|
||||||
|
keys = [Reline::Key.new(s.ord, s.ord, false)]
|
||||||
|
else
|
||||||
|
keys = []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[keys, input.drop(matched_bytes.size)]
|
[keys, input.drop(matched_bytes.size)]
|
||||||
|
@ -265,7 +265,6 @@ class Reline::LineEditor
|
|||||||
@line_index = 0
|
@line_index = 0
|
||||||
@cache.clear
|
@cache.clear
|
||||||
@line_backup_in_history = nil
|
@line_backup_in_history = nil
|
||||||
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def multiline_on
|
def multiline_on
|
||||||
@ -1036,20 +1035,11 @@ class Reline::LineEditor
|
|||||||
end
|
end
|
||||||
|
|
||||||
private def normal_char(key)
|
private def normal_char(key)
|
||||||
@multibyte_buffer << key.combined_char
|
if key.char < 0x80
|
||||||
if @multibyte_buffer.size > 1
|
|
||||||
if @multibyte_buffer.dup.force_encoding(encoding).valid_encoding?
|
|
||||||
process_key(@multibyte_buffer.dup.force_encoding(encoding), nil)
|
|
||||||
@multibyte_buffer.clear
|
|
||||||
else
|
|
||||||
# invalid
|
|
||||||
return
|
|
||||||
end
|
|
||||||
else # single 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)
|
||||||
process_key(key.combined_char, method_symbol)
|
process_key(key.combined_char, method_symbol)
|
||||||
@multibyte_buffer.clear
|
else
|
||||||
|
process_key(key.char.chr(encoding), nil)
|
||||||
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
|
||||||
byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
|
||||||
@ -1526,7 +1516,6 @@ class Reline::LineEditor
|
|||||||
|
|
||||||
private def generate_searcher(search_key)
|
private def generate_searcher(search_key)
|
||||||
search_word = String.new(encoding: encoding)
|
search_word = String.new(encoding: encoding)
|
||||||
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
|
|
||||||
hit_pointer = nil
|
hit_pointer = nil
|
||||||
lambda do |key|
|
lambda do |key|
|
||||||
search_again = false
|
search_again = false
|
||||||
@ -1541,11 +1530,7 @@ class Reline::LineEditor
|
|||||||
search_again = true if search_key == key
|
search_again = true if search_key == key
|
||||||
search_key = key
|
search_key = key
|
||||||
else
|
else
|
||||||
multibyte_buf << key
|
search_word << key
|
||||||
if multibyte_buf.dup.force_encoding(encoding).valid_encoding?
|
|
||||||
search_word << multibyte_buf.dup.force_encoding(encoding)
|
|
||||||
multibyte_buf.clear
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
hit = nil
|
hit = nil
|
||||||
if not search_word.empty? and @line_backup_in_history&.include?(search_word)
|
if not search_word.empty? and @line_backup_in_history&.include?(search_word)
|
||||||
|
@ -121,17 +121,15 @@ class Reline::TestCase < Test::Unit::TestCase
|
|||||||
@line_editor.input_key(Reline::Key.new(byte, byte, false))
|
@line_editor.input_key(Reline::Key.new(byte, byte, false))
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
c.bytes.each do |b|
|
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
|
||||||
@line_editor.input_key(Reline::Key.new(b, b, false))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def input_raw_keys(input, convert = true)
|
def input_raw_keys(input, convert = true)
|
||||||
input = convert_str(input) if convert
|
input = convert_str(input) if convert
|
||||||
input.bytes.each do |b|
|
input.chars.each do |c|
|
||||||
@line_editor.input_key(Reline::Key.new(b, b, false))
|
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def encoding
|
||||||
|
Reline.core.encoding
|
||||||
|
end
|
||||||
|
|
||||||
def test_match_status
|
def test_match_status
|
||||||
config = Reline::Config.new
|
config = Reline::Config.new
|
||||||
{
|
{
|
||||||
@ -23,7 +27,7 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
}.each_pair do |key, func|
|
}.each_pair do |key, func|
|
||||||
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, encoding)
|
||||||
assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".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::MATCHING_MATCHED, stroke.match_status("ab".bytes))
|
||||||
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("abc".bytes))
|
||||||
@ -37,7 +41,7 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
def test_match_unknown
|
def test_match_unknown
|
||||||
config = Reline::Config.new
|
config = Reline::Config.new
|
||||||
config.add_default_key_binding("\e[9abc".bytes, 'x')
|
config.add_default_key_binding("\e[9abc".bytes, 'x')
|
||||||
stroke = Reline::KeyStroke.new(config)
|
stroke = Reline::KeyStroke.new(config, encoding)
|
||||||
sequences = [
|
sequences = [
|
||||||
"\e[9abc",
|
"\e[9abc",
|
||||||
"\e[9d",
|
"\e[9d",
|
||||||
@ -66,7 +70,7 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
}.each_pair do |key, func|
|
}.each_pair do |key, func|
|
||||||
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, encoding)
|
||||||
assert_equal(['123'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abcde'.bytes))
|
assert_equal(['123'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abcde'.bytes))
|
||||||
assert_equal(['456'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abde'.bytes))
|
assert_equal(['456'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abde'.bytes))
|
||||||
# CSI sequence
|
# CSI sequence
|
||||||
@ -83,7 +87,7 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
}.each_pair do |key, func|
|
}.each_pair do |key, func|
|
||||||
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, encoding)
|
||||||
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
|
||||||
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes))
|
||||||
end
|
end
|
||||||
@ -96,10 +100,27 @@ class Reline::KeyStroke::Test < Reline::TestCase
|
|||||||
}.each_pair do |key, func|
|
}.each_pair do |key, func|
|
||||||
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, encoding)
|
||||||
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
|
||||||
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".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::UNMATCHED, stroke.match_status([32, 195, 164]))
|
||||||
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164]))
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status([195, 164]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_multibyte_matching
|
||||||
|
config = Reline::Config.new
|
||||||
|
stroke = Reline::KeyStroke.new(config, encoding)
|
||||||
|
char = 'あ'.encode(encoding)
|
||||||
|
key = Reline::Key.new(char.ord, char.ord, false)
|
||||||
|
bytes = char.bytes
|
||||||
|
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(bytes))
|
||||||
|
assert_equal([[key], []], stroke.expand(bytes))
|
||||||
|
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(bytes * 2))
|
||||||
|
assert_equal([[key], bytes], stroke.expand(bytes * 2))
|
||||||
|
(1...bytes.size).each do |i|
|
||||||
|
partial_bytes = bytes.take(i)
|
||||||
|
assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status(partial_bytes))
|
||||||
|
assert_equal([[], []], stroke.expand(partial_bytes))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user