Skip to content

Commit 8811a1d

Browse files
committed
KeyStroke handles multibyte character
1 parent 2d80784 commit 8811a1d

File tree

5 files changed

+51
-38
lines changed

5 files changed

+51
-38
lines changed

lib/reline.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ def self.encoding_system_needs
483483
def self.core
484484
@core ||= Core.new { |core|
485485
core.config = Reline::Config.new
486-
core.key_stroke = Reline::KeyStroke.new(core.config)
486+
core.key_stroke = Reline::KeyStroke.new(core.config, core.encoding)
487487
core.line_editor = Reline::LineEditor.new(core.config, core.encoding)
488488

489489
core.basic_word_break_characters = " \t\n`><=;|&{("

lib/reline/key_stroke.rb

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ class Reline::KeyStroke
33
CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
44
CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
55

6-
def initialize(config)
6+
def initialize(config, encoding)
77
@config = config
8+
@encoding = encoding
89
end
910

1011
def match_status(input)
1112
matching = key_mapping.matching?(input)
1213
matched = key_mapping.get(input)
1314

1415
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
15-
matched ||= input.size == 1
16+
matched ||= input.size == 1 && input[0] < 0x80
1617
matching ||= input == [ESC_BYTE]
1718

1819
if matching && matched
@@ -23,10 +24,14 @@ def match_status(input)
2324
:matched
2425
elsif input[0] == ESC_BYTE
2526
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
26-
elsif input.size == 1
27-
:matched
2827
else
29-
:unmatched
28+
s = input.pack('c*').force_encoding(@encoding)
29+
if s.valid_encoding?
30+
s.size == 1 ? :matched : :unmatched
31+
else
32+
# Invalid string is :matching (part of valid string) or :matched (invalid bytes to be ignored)
33+
:matching_matched
34+
end
3035
end
3136
end
3237

@@ -36,6 +41,7 @@ def expand(input)
3641
bytes = input.take(i)
3742
status = match_status(bytes)
3843
matched_bytes = bytes if status == :matched || status == :matching_matched
44+
break if status == :matched || status == :unmatched
3945
end
4046
return [[], []] unless matched_bytes
4147

@@ -44,12 +50,15 @@ def expand(input)
4450
keys = func.map { |c| Reline::Key.new(c, c, false) }
4551
elsif func
4652
keys = [Reline::Key.new(func, func, false)]
47-
elsif matched_bytes.size == 1
48-
keys = [Reline::Key.new(input.first, input.first, false)]
4953
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
5054
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
5155
else
52-
keys = []
56+
s = matched_bytes.pack('c*').force_encoding(@encoding)
57+
if s.valid_encoding? && s.size == 1
58+
keys = [Reline::Key.new(s.ord, s.ord, false)]
59+
else
60+
keys = []
61+
end
5362
end
5463

5564
[keys, input.drop(matched_bytes.size)]

lib/reline/line_editor.rb

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,6 @@ def reset_line
262262
@line_index = 0
263263
@cache.clear
264264
@line_backup_in_history = nil
265-
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
266265
end
267266

268267
def multiline_on
@@ -1066,20 +1065,11 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
10661065
end
10671066

10681067
private def normal_char(key)
1069-
@multibyte_buffer << key.combined_char
1070-
if @multibyte_buffer.size > 1
1071-
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1072-
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1073-
@multibyte_buffer.clear
1074-
else
1075-
# invalid
1076-
return
1077-
end
1078-
else # single byte
1079-
return if key.char >= 128 # maybe, first byte of multi byte
1068+
if key.char < 0x80
10801069
method_symbol = @config.editing_mode.get_method(key.combined_char)
10811070
process_key(key.combined_char, method_symbol)
1082-
@multibyte_buffer.clear
1071+
else
1072+
process_key(key.char.chr(@encoding), nil)
10831073
end
10841074
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
10851075
byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
@@ -1592,7 +1582,6 @@ def finish
15921582

15931583
private def generate_searcher(search_key)
15941584
search_word = String.new(encoding: @encoding)
1595-
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
15961585
hit_pointer = nil
15971586
lambda do |key|
15981587
search_again = false
@@ -1607,11 +1596,7 @@ def finish
16071596
search_again = true if search_key == key
16081597
search_key = key
16091598
else
1610-
multibyte_buf << key
1611-
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1612-
search_word << multibyte_buf.dup.force_encoding(@encoding)
1613-
multibyte_buf.clear
1614-
end
1599+
search_word << key
16151600
end
16161601
hit = nil
16171602
if not search_word.empty? and @line_backup_in_history&.include?(search_word)

test/reline/helper.rb

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,15 @@ def input_keys(input, convert = true)
114114
@line_editor.input_key(Reline::Key.new(byte, byte, false))
115115
end
116116
else
117-
c.bytes.each do |b|
118-
@line_editor.input_key(Reline::Key.new(b, b, false))
119-
end
117+
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
120118
end
121119
end
122120
end
123121

124122
def input_raw_keys(input, convert = true)
125123
input = convert_str(input) if convert
126-
input.bytes.each do |b|
127-
@line_editor.input_key(Reline::Key.new(b, b, false))
124+
input.chars.each do |c|
125+
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
128126
end
129127
end
130128

test/reline/test_key_stroke.rb

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ def to_keys
1313
end
1414
}
1515

16+
def encoding
17+
Reline.core.encoding
18+
end
19+
1620
def test_match_status
1721
config = Reline::Config.new
1822
{
@@ -23,7 +27,7 @@ def test_match_status
2327
}.each_pair do |key, func|
2428
config.add_default_key_binding(key.bytes, func.bytes)
2529
end
26-
stroke = Reline::KeyStroke.new(config)
30+
stroke = Reline::KeyStroke.new(config, encoding)
2731
assert_equal(:matching_matched, stroke.match_status("a".bytes))
2832
assert_equal(:matching_matched, stroke.match_status("ab".bytes))
2933
assert_equal(:matched, stroke.match_status("abc".bytes))
@@ -37,7 +41,7 @@ def test_match_status
3741
def test_match_unknown
3842
config = Reline::Config.new
3943
config.add_default_key_binding("\e[9abc".bytes, 'x')
40-
stroke = Reline::KeyStroke.new(config)
44+
stroke = Reline::KeyStroke.new(config, encoding)
4145
sequences = [
4246
"\e[9abc",
4347
"\e[9d",
@@ -66,7 +70,7 @@ def test_expand
6670
}.each_pair do |key, func|
6771
config.add_default_key_binding(key.bytes, func.bytes)
6872
end
69-
stroke = Reline::KeyStroke.new(config)
73+
stroke = Reline::KeyStroke.new(config, encoding)
7074
assert_equal(['123'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abcde'.bytes))
7175
assert_equal(['456'.bytes.map { |c| Reline::Key.new(c, c, false) }, 'de'.bytes], stroke.expand('abde'.bytes))
7276
# CSI sequence
@@ -83,7 +87,7 @@ def test_oneshot_key_bindings
8387
}.each_pair do |key, func|
8488
config.add_default_key_binding(key.bytes, func.bytes)
8589
end
86-
stroke = Reline::KeyStroke.new(config)
90+
stroke = Reline::KeyStroke.new(config, encoding)
8791
assert_equal(:unmatched, stroke.match_status('zzz'.bytes))
8892
assert_equal(:matched, stroke.match_status('abc'.bytes))
8993
end
@@ -96,10 +100,27 @@ def test_with_reline_key
96100
}.each_pair do |key, func|
97101
config.add_oneshot_key_binding(key, func.bytes)
98102
end
99-
stroke = Reline::KeyStroke.new(config)
103+
stroke = Reline::KeyStroke.new(config, encoding)
100104
assert_equal(:unmatched, stroke.match_status('da'.bytes))
101105
assert_equal(:matched, stroke.match_status("\eda".bytes))
102106
assert_equal(:unmatched, stroke.match_status([32, 195, 164]))
103107
assert_equal(:matched, stroke.match_status([195, 164]))
104108
end
109+
110+
def test_multibyte_matching
111+
config = Reline::Config.new
112+
stroke = Reline::KeyStroke.new(config, encoding)
113+
char = 'あ'.encode(encoding)
114+
key = Reline::Key.new(char.ord, char.ord, false)
115+
bytes = char.bytes
116+
assert_equal(:matched, stroke.match_status(bytes))
117+
assert_equal([[key], []], stroke.expand(bytes))
118+
assert_equal(:unmatched, stroke.match_status(bytes * 2))
119+
assert_equal([[key], bytes], stroke.expand(bytes * 2))
120+
(1...bytes.size).each do |i|
121+
partial_bytes = bytes.take(i)
122+
assert_equal(:matching_matched, stroke.match_status(partial_bytes))
123+
assert_equal([[], []], stroke.expand(partial_bytes))
124+
end
125+
end
105126
end

0 commit comments

Comments
 (0)