Skip to content

Commit bace10d

Browse files
committed
KeyStroke handles multibyte character
1 parent e3c73bb commit bace10d

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,8 +3,9 @@ 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
# Input exactly matches to a key sequence
@@ -21,7 +22,7 @@ def match_status(input)
2122
matched = key_mapping.get(input)
2223

2324
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
24-
matched ||= input.size == 1
25+
matched ||= input.size == 1 && input[0] < 0x80
2526
matching ||= input == [ESC_BYTE]
2627

2728
if matching && matched
@@ -32,10 +33,14 @@ def match_status(input)
3233
MATCHED
3334
elsif input[0] == ESC_BYTE
3435
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
35-
elsif input.size == 1
36-
MATCHED
3736
else
38-
UNMATCHED
37+
s = input.pack('c*').force_encoding(@encoding)
38+
if s.valid_encoding?
39+
s.size == 1 ? MATCHED : UNMATCHED
40+
else
41+
# Invalid string is MATCHING (part of valid string) or MATCHED (invalid bytes to be ignored)
42+
MATCHING_MATCHED
43+
end
3944
end
4045
end
4146

@@ -45,6 +50,7 @@ def expand(input)
4550
bytes = input.take(i)
4651
status = match_status(bytes)
4752
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
53+
break if status == MATCHED || status == UNMATCHED
4854
end
4955
return [[], []] unless matched_bytes
5056

@@ -53,12 +59,15 @@ def expand(input)
5359
keys = func.map { |c| Reline::Key.new(c, c, false) }
5460
elsif func
5561
keys = [Reline::Key.new(func, func, false)]
56-
elsif matched_bytes.size == 1
57-
keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
5862
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
5963
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
6064
else
61-
keys = []
65+
s = matched_bytes.pack('c*').force_encoding(@encoding)
66+
if s.valid_encoding? && s.size == 1
67+
keys = [Reline::Key.new(s.ord, s.ord, false)]
68+
else
69+
keys = []
70+
end
6271
end
6372

6473
[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
@@ -264,7 +264,6 @@ def reset_line
264264
@line_index = 0
265265
@cache.clear
266266
@line_backup_in_history = nil
267-
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
268267
end
269268

270269
def multiline_on
@@ -1068,20 +1067,11 @@ def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
10681067
end
10691068

10701069
private def normal_char(key)
1071-
@multibyte_buffer << key.combined_char
1072-
if @multibyte_buffer.size > 1
1073-
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
1074-
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
1075-
@multibyte_buffer.clear
1076-
else
1077-
# invalid
1078-
return
1079-
end
1080-
else # single byte
1081-
return if key.char >= 128 # maybe, first byte of multi byte
1070+
if key.char < 0x80
10821071
method_symbol = @config.editing_mode.get_method(key.combined_char)
10831072
process_key(key.combined_char, method_symbol)
1084-
@multibyte_buffer.clear
1073+
else
1074+
process_key(key.char.chr(@encoding), nil)
10851075
end
10861076
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
10871077
byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
@@ -1598,7 +1588,6 @@ def finish
15981588

15991589
private def generate_searcher(search_key)
16001590
search_word = String.new(encoding: @encoding)
1601-
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
16021591
hit_pointer = nil
16031592
lambda do |key|
16041593
search_again = false
@@ -1613,11 +1602,7 @@ def finish
16131602
search_again = true if search_key == key
16141603
search_key = key
16151604
else
1616-
multibyte_buf << key
1617-
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
1618-
search_word << multibyte_buf.dup.force_encoding(@encoding)
1619-
multibyte_buf.clear
1620-
end
1605+
search_word << key
16211606
end
16221607
hit = nil
16231608
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
@@ -121,17 +121,15 @@ def input_keys(input, convert = true)
121121
@line_editor.input_key(Reline::Key.new(byte, byte, false))
122122
end
123123
else
124-
c.bytes.each do |b|
125-
@line_editor.input_key(Reline::Key.new(b, b, false))
126-
end
124+
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
127125
end
128126
end
129127
end
130128

131129
def input_raw_keys(input, convert = true)
132130
input = convert_str(input) if convert
133-
input.bytes.each do |b|
134-
@line_editor.input_key(Reline::Key.new(b, b, false))
131+
input.chars.each do |c|
132+
@line_editor.input_key(Reline::Key.new(c.ord, c.ord, false))
135133
end
136134
end
137135

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(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("a".bytes))
2832
assert_equal(Reline::KeyStroke::MATCHING_MATCHED, stroke.match_status("ab".bytes))
2933
assert_equal(Reline::KeyStroke::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(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes))
8892
assert_equal(Reline::KeyStroke::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(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.bytes))
101105
assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("\eda".bytes))
102106
assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status([32, 195, 164]))
103107
assert_equal(Reline::KeyStroke::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(Reline::KeyStroke::MATCHED, stroke.match_status(bytes))
117+
assert_equal([[key], []], stroke.expand(bytes))
118+
assert_equal(Reline::KeyStroke::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(Reline::KeyStroke::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)