diff options
author | aycabta <aycabta@gmail.com> | 2019-05-24 23:38:40 +0900 |
---|---|---|
committer | aycabta <aycabta@gmail.com> | 2019-05-24 23:38:40 +0900 |
commit | eb4e774711b17b8a25a7b16c4f0b6044da3dc261 (patch) | |
tree | d33f0bfb2087ee0169422d08b8553678fd189f75 | |
parent | 2d34087a38ac680c5576a56fbf4104c3561b0204 (diff) |
Support Meta key in Reline
-rw-r--r-- | lib/reline.rb | 6 | ||||
-rw-r--r-- | lib/reline/ansi.rb | 8 | ||||
-rw-r--r-- | lib/reline/config.rb | 4 | ||||
-rw-r--r-- | lib/reline/general_io.rb | 7 | ||||
-rw-r--r-- | lib/reline/key_actor/emacs.rb | 4 | ||||
-rw-r--r-- | lib/reline/key_actor/vi_insert.rb | 256 | ||||
-rw-r--r-- | lib/reline/key_stroke.rb | 89 | ||||
-rw-r--r-- | lib/reline/line_editor.rb | 67 | ||||
-rw-r--r-- | lib/reline/windows.rb | 4 | ||||
-rw-r--r-- | test/reline/helper.rb | 8 | ||||
-rw-r--r-- | test/reline/test_key_stroke.rb | 46 |
11 files changed, 282 insertions, 217 deletions
diff --git a/lib/reline.rb b/lib/reline.rb index ac279b43f0..41f492d50c 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -1,4 +1,5 @@ require 'io/console' +require 'timeout' require 'reline/version' require 'reline/config' require 'reline/key_actor' @@ -6,6 +7,8 @@ require 'reline/key_stroke' require 'reline/line_editor' module Reline + Key = Struct.new('Key', :char, :combined_char, :with_meta) + extend self FILENAME_COMPLETION_PROC = nil USERNAME_COMPLETION_PROC = nil @@ -321,8 +324,7 @@ module Reline key_stroke = Reline::KeyStroke.new(config) begin loop do - c = Reline::IOGate.getc - key_stroke.input_to!(c)&.then { |inputs| + key_stroke.read_io(@@config.keyseq_timeout) { |inputs| inputs.each { |c| @@line_editor.input_key(c) @@line_editor.rerender diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index 34720d352c..1ead89b8ff 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -9,7 +9,11 @@ class Reline::ANSI @@output = val end + @@buf = [] def self.getc + unless @@buf.empty? + return @@buf.shift + end c = nil loop do result = select([@@input], [], [], 0.1) @@ -20,6 +24,10 @@ class Reline::ANSI c&.ord end + def self.ungetc(c) + @@buf.unshift(c) + end + def self.get_screen_size @@input.winsize rescue Errno::ENOTTY diff --git a/lib/reline/config.rb b/lib/reline/config.rb index cf46eda5ea..120a95a6fa 100644 --- a/lib/reline/config.rb +++ b/lib/reline/config.rb @@ -16,6 +16,7 @@ class Reline::Config history-size horizontal-scroll-mode input-meta + keyseq-timeout mark-directories mark-modified-lines mark-symlinked-directories @@ -44,6 +45,7 @@ class Reline::Config @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new @history_size = 500 + @keyseq_timeout = 500 end def reset @@ -178,6 +180,8 @@ class Reline::Config when 'vi-insert' @keymap_label = :vi_insert end + when 'keyseq-timeout' + @keyseq_timeout = value.to_i end end diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb index 0ea31b972b..526c780084 100644 --- a/lib/reline/general_io.rb +++ b/lib/reline/general_io.rb @@ -8,6 +8,9 @@ class Reline::GeneralIO end def self.getc + unless @@buf.empty? + return @@buf.shift + end c = nil loop do result = select([@@input], [], [], 0.1) @@ -18,6 +21,10 @@ class Reline::GeneralIO c&.ord end + def self.ungetc(c) + @@buf.unshift(c) + end + def self.get_screen_size [1, 1] end diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb index 5dac1ab12b..5a847169c4 100644 --- a/lib/reline/key_actor/emacs.rb +++ b/lib/reline/key_actor/emacs.rb @@ -277,13 +277,13 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base # 137 M-^I :ed_unassigned, # 138 M-^J - :ed_unassigned, + :key_newline, # 139 M-^K :ed_unassigned, # 140 M-^L :ed_clear_screen, # 141 M-^M - :ed_unassigned, + :key_newline, # 142 M-^N :ed_unassigned, # 143 M-^O diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb index 8585a642ab..0301e5d20d 100644 --- a/lib/reline/key_actor/vi_insert.rb +++ b/lib/reline/key_actor/vi_insert.rb @@ -257,261 +257,261 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base # 127 ^? :vi_delete_prev_char, # 128 M-^@ - :ed_insert, + :ed_unassigned, # 129 M-^A - :ed_insert, + :ed_unassigned, # 130 M-^B - :ed_insert, + :ed_unassigned, # 131 M-^C - :ed_insert, + :ed_unassigned, # 132 M-^D - :ed_insert, + :ed_unassigned, # 133 M-^E - :ed_insert, + :ed_unassigned, # 134 M-^F - :ed_insert, + :ed_unassigned, # 135 M-^G - :ed_insert, + :ed_unassigned, # 136 M-^H - :ed_insert, + :ed_unassigned, # 137 M-^I - :ed_insert, + :ed_unassigned, # 138 M-^J - :ed_insert, + :key_newline, # 139 M-^K - :ed_insert, + :ed_unassigned, # 140 M-^L - :ed_insert, + :ed_unassigned, # 141 M-^M - :ed_insert, + :key_newline, # 142 M-^N - :ed_insert, + :ed_unassigned, # 143 M-^O - :ed_insert, + :ed_unassigned, # 144 M-^P - :ed_insert, + :ed_unassigned, # 145 M-^Q - :ed_insert, + :ed_unassigned, # 146 M-^R - :ed_insert, + :ed_unassigned, # 147 M-^S - :ed_insert, + :ed_unassigned, # 148 M-^T - :ed_insert, + :ed_unassigned, # 149 M-^U - :ed_insert, + :ed_unassigned, # 150 M-^V - :ed_insert, + :ed_unassigned, # 151 M-^W - :ed_insert, + :ed_unassigned, # 152 M-^X - :ed_insert, + :ed_unassigned, # 153 M-^Y - :ed_insert, + :ed_unassigned, # 154 M-^Z - :ed_insert, + :ed_unassigned, # 155 M-^[ - :ed_insert, + :ed_unassigned, # 156 M-^\ - :ed_insert, + :ed_unassigned, # 157 M-^] - :ed_insert, + :ed_unassigned, # 158 M-^^ - :ed_insert, + :ed_unassigned, # 159 M-^_ - :ed_insert, + :ed_unassigned, # 160 M-SPACE - :ed_insert, + :ed_unassigned, # 161 M-! - :ed_insert, + :ed_unassigned, # 162 M-" - :ed_insert, + :ed_unassigned, # 163 M-# - :ed_insert, + :ed_unassigned, # 164 M-$ - :ed_insert, + :ed_unassigned, # 165 M-% - :ed_insert, + :ed_unassigned, # 166 M-& - :ed_insert, + :ed_unassigned, # 167 M-' - :ed_insert, + :ed_unassigned, # 168 M-( - :ed_insert, + :ed_unassigned, # 169 M-) - :ed_insert, + :ed_unassigned, # 170 M-* - :ed_insert, + :ed_unassigned, # 171 M-+ - :ed_insert, + :ed_unassigned, # 172 M-, - :ed_insert, + :ed_unassigned, # 173 M-- - :ed_insert, + :ed_unassigned, # 174 M-. - :ed_insert, + :ed_unassigned, # 175 M-/ - :ed_insert, + :ed_unassigned, # 176 M-0 - :ed_insert, + :ed_unassigned, # 177 M-1 - :ed_insert, + :ed_unassigned, # 178 M-2 - :ed_insert, + :ed_unassigned, # 179 M-3 - :ed_insert, + :ed_unassigned, # 180 M-4 - :ed_insert, + :ed_unassigned, # 181 M-5 - :ed_insert, + :ed_unassigned, # 182 M-6 - :ed_insert, + :ed_unassigned, # 183 M-7 - :ed_insert, + :ed_unassigned, # 184 M-8 - :ed_insert, + :ed_unassigned, # 185 M-9 - :ed_insert, + :ed_unassigned, # 186 M-: - :ed_insert, + :ed_unassigned, # 187 M-; - :ed_insert, + :ed_unassigned, # 188 M-< - :ed_insert, + :ed_unassigned, # 189 M-= - :ed_insert, + :ed_unassigned, # 190 M-> - :ed_insert, + :ed_unassigned, # 191 M-? - :ed_insert, + :ed_unassigned, # 192 M-@ - :ed_insert, + :ed_unassigned, # 193 M-A - :ed_insert, + :ed_unassigned, # 194 M-B - :ed_insert, + :ed_unassigned, # 195 M-C - :ed_insert, + :ed_unassigned, # 196 M-D - :ed_insert, + :ed_unassigned, # 197 M-E - :ed_insert, + :ed_unassigned, # 198 M-F - :ed_insert, + :ed_unassigned, # 199 M-G - :ed_insert, + :ed_unassigned, # 200 M-H - :ed_insert, + :ed_unassigned, # 201 M-I - :ed_insert, + :ed_unassigned, # 202 M-J - :ed_insert, + :ed_unassigned, # 203 M-K - :ed_insert, + :ed_unassigned, # 204 M-L - :ed_insert, + :ed_unassigned, # 205 M-M - :ed_insert, + :ed_unassigned, # 206 M-N - :ed_insert, + :ed_unassigned, # 207 M-O - :ed_insert, + :ed_unassigned, # 208 M-P - :ed_insert, + :ed_unassigned, # 209 M-Q - :ed_insert, + :ed_unassigned, # 210 M-R - :ed_insert, + :ed_unassigned, # 211 M-S - :ed_insert, + :ed_unassigned, # 212 M-T - :ed_insert, + :ed_unassigned, # 213 M-U - :ed_insert, + :ed_unassigned, # 214 M-V - :ed_insert, + :ed_unassigned, # 215 M-W - :ed_insert, + :ed_unassigned, # 216 M-X - :ed_insert, + :ed_unassigned, # 217 M-Y - :ed_insert, + :ed_unassigned, # 218 M-Z - :ed_insert, + :ed_unassigned, # 219 M-[ - :ed_insert, + :ed_unassigned, # 220 M-\ - :ed_insert, + :ed_unassigned, # 221 M-] - :ed_insert, + :ed_unassigned, # 222 M-^ - :ed_insert, + :ed_unassigned, # 223 M-_ - :ed_insert, + :ed_unassigned, # 223 M-` - :ed_insert, + :ed_unassigned, # 224 M-a - :ed_insert, + :ed_unassigned, # 225 M-b - :ed_insert, + :ed_unassigned, # 226 M-c - :ed_insert, + :ed_unassigned, # 227 M-d - :ed_insert, + :ed_unassigned, # 228 M-e - :ed_insert, + :ed_unassigned, # 229 M-f - :ed_insert, + :ed_unassigned, # 230 M-g - :ed_insert, + :ed_unassigned, # 231 M-h - :ed_insert, + :ed_unassigned, # 232 M-i - :ed_insert, + :ed_unassigned, # 233 M-j - :ed_insert, + :ed_unassigned, # 234 M-k - :ed_insert, + :ed_unassigned, # 235 M-l - :ed_insert, + :ed_unassigned, # 236 M-m - :ed_insert, + :ed_unassigned, # 237 M-n - :ed_insert, + :ed_unassigned, # 238 M-o - :ed_insert, + :ed_unassigned, # 239 M-p - :ed_insert, + :ed_unassigned, # 240 M-q - :ed_insert, + :ed_unassigned, # 241 M-r - :ed_insert, + :ed_unassigned, # 242 M-s - :ed_insert, + :ed_unassigned, # 243 M-t - :ed_insert, + :ed_unassigned, # 244 M-u - :ed_insert, + :ed_unassigned, # 245 M-v - :ed_insert, + :ed_unassigned, # 246 M-w - :ed_insert, + :ed_unassigned, # 247 M-x - :ed_insert, + :ed_unassigned, # 248 M-y - :ed_insert, + :ed_unassigned, # 249 M-z - :ed_insert, + :ed_unassigned, # 250 M-{ - :ed_insert, + :ed_unassigned, # 251 M-| - :ed_insert, + :ed_unassigned, # 252 M-} - :ed_insert, + :ed_unassigned, # 253 M-~ - :ed_insert, + :ed_unassigned, # 254 M-^? - :ed_insert + :ed_unassigned # 255 # EOF ] diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb index fdfe74a6ee..d09abd9058 100644 --- a/lib/reline/key_stroke.rb +++ b/lib/reline/key_stroke.rb @@ -13,30 +13,81 @@ class Reline::KeyStroke def initialize(config) @config = config - @buffer = [] end - def input_to(bytes) - case match_status(bytes) - when :matching - nil - when :matched - expand(bytes) - when :unmatched - bytes + # Keystrokes of GNU Readline will timeout it with the specification of + # "keyseq-timeout" when waiting for the 2nd character after the 1st one. + # If the 2nd character comes after 1st ESC without timeout it has a + # meta-property of meta-key to discriminate modified key with meta-key + # from multibyte characters that come with 8th bit on. + # + # GNU Readline will wait for the 2nd character with "keyseq-timeout" + # milli-seconds but wait forever after 3rd characters. + def read_io(keyseq_timeout, &block) + buffer = [] + loop do + c = Reline::IOGate.getc + buffer << c + result = match_status(buffer) + case result + when :matched + block.(expand(bytes).map{ |c| Reline::Key.new(c, c, false) }) + break + when :matching + if buffer.size == 1 + begin + succ_c = nil + Timeout.timeout(keyseq_timeout / 1000.0) { + succ_c = Reline::IOGate.getc + } + rescue Timeout::Error # cancel matching only when first byte + block.([Reline::Key.new(c, c, false)]) + break + else + if match_status(buffer.dup.push(succ_c)) == :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 + break + else + Reline::IOGate.ungetc(succ_c) + end + end + end + when :unmatched + if buffer.size == 1 and c == "\e".ord + read_escaped_key(buffer, block) + else + block.(buffer.map{ |c| Reline::Key.new(c, c, false) }) + end + break + end end end - def input_to!(bytes) - if bytes.nil? - return @buffer.push(nil)&.tap { clear } + def read_escaped_key(buffer, block) + begin + escaped_c = nil + Timeout.timeout(keyseq_timeout / 1000.0) { + escaped_c = Reline::IOGate.getc + } + rescue Timeout::Error # independent ESC + block.([Reline::Key.new(c, c, false)]) + else + 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 - @buffer.concat Array(bytes) - input_to(@buffer)&.tap { clear } end - private - def match_status(input) key_mapping.keys.select { |lhs| lhs.start_with? input @@ -53,6 +104,8 @@ class Reline::KeyStroke } end + private + def expand(input) lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first return input unless lhs @@ -70,8 +123,4 @@ class Reline::KeyStroke def key_mapping @config[:key_mapping].transform_keys(&:bytes) end - - def clear - @buffer = [] - end end diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 92f75c7b88..764176c167 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -104,7 +104,6 @@ class Reline::LineEditor @kill_ring = Reline::KillRing.new @vi_clipboard = '' @vi_arg = nil - @meta_prefix = false @waiting_proc = nil @waiting_operator_proc = nil @completion_journey_data = nil @@ -625,31 +624,38 @@ class Reline::LineEditor private def normal_char(key) method_symbol = method_obj = nil - @multibyte_buffer << key + @multibyte_buffer << key.combined_char if @multibyte_buffer.size > 1 if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding? - key = @multibyte_buffer.dup.force_encoding(@encoding) + process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil, nil) @multibyte_buffer.clear else # invalid return end else # single byte - return if key >= 128 # maybe, first byte of multi byte - if @meta_prefix - key |= 0b10000000 if key.nobits?(0b10000000) - @meta_prefix = false - end - method_symbol = @config.editing_mode.get_method(key) - if key.allbits?(0b10000000) and method_symbol == :ed_unassigned - return # This is unknown input - end - if method_symbol and respond_to?(method_symbol, true) - method_obj = method(method_symbol) + 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 + # split ESC + key + method_symbol = @config.editing_mode.get_method("\e".ord) + if method_symbol and respond_to?(method_symbol, true) + method_obj = method(method_symbol) + end + process_key("\e".ord, method_symbol, method_obj) + method_symbol = @config.editing_mode.get_method(key.char) + if method_symbol and respond_to?(method_symbol, true) + method_obj = method(method_symbol) + end + process_key(key.char, method_symbol, method_obj) + else + if method_symbol and respond_to?(method_symbol, true) + method_obj = method(method_symbol) + end + process_key(key.combined_char, method_symbol, method_obj) end @multibyte_buffer.clear end - process_key(key, method_symbol, method_obj) if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer) @byte_pointer -= byte_size @@ -660,7 +666,7 @@ class Reline::LineEditor end def input_key(key) - if key.nil? + if key.nil? or key.char.nil? if @first_char @line = nil end @@ -669,30 +675,20 @@ class Reline::LineEditor end @first_char = false completion_occurs = false - if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\C-i".ord + if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord result = @completion_proc&.(@line) if result.is_a?(Array) completion_occurs = true complete(result) end - elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key) + elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char) result = @completion_proc&.(@line) if result.is_a?(Array) completion_occurs = true - move_completed_list(result, "\C-p".ord == key ? :up : :down) - end - elsif @config.editing_mode_is?(:emacs) and key == "\e".ord # meta key - if @meta_prefix - # escape twice - @meta_prefix = false - @kill_ring.process - else - @meta_prefix = true + move_completed_list(result, "\C-p".ord == key.char ? :up : :down) end - elsif @config.editing_mode_is?(:vi_command) and key == "\e".ord - # suppress ^[ when command_mode - elsif Symbol === key and respond_to?(key, true) - process_key(key, key, method(key)) + elsif Symbol === key.char and respond_to?(key.char, true) + process_key(key.char, key.char, method(key.char)) else normal_char(key) end @@ -823,6 +819,15 @@ class Reline::LineEditor end end + private def key_newline(key) + if @is_multiline + next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer) + cursor_line = @line.byteslice(0, @byte_pointer) + insert_new_line(cursor_line, next_line) + @cursor = 0 + end + end + private def ed_insert(key) if key.instance_of?(String) width = Reline::Unicode.get_mbchar_width(key) diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb index 2e7ed89ff9..842760e5bd 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/windows.rb @@ -101,6 +101,10 @@ class Reline::Windows end end + def self.ungetc(c) + @@buf.unshift(c) + end + def self.get_screen_size csbi = 0.chr * 24 @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) diff --git a/test/reline/helper.rb b/test/reline/helper.rb index 227fc0689a..e6587b30f6 100644 --- a/test/reline/helper.rb +++ b/test/reline/helper.rb @@ -40,13 +40,13 @@ class Reline::TestCase < Test::Unit::TestCase eighth_bit = 0b10000000 byte = c.bytes.first if byte.allbits?(eighth_bit) - @line_editor.input_key("\e".ord) - byte ^= eighth_bit + @line_editor.input_key(Reline::Key.new(byte ^ eighth_bit, byte, true)) + else + @line_editor.input_key(Reline::Key.new(byte, byte, false)) end - @line_editor.input_key(byte) else c.bytes.each do |b| - @line_editor.input_key(b) + @line_editor.input_key(Reline::Key.new(b, b, false)) end end end diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb index b6d5ce4150..e76129d04b 100644 --- a/test/reline/test_key_stroke.rb +++ b/test/reline/test_key_stroke.rb @@ -4,30 +4,16 @@ class Reline::KeyStroke::Test < Reline::TestCase using Module.new { refine Array do def as_s - map(&:chr).join + join + end + + def to_keys + map{ |b| Reline::Key.new(b, b, false) } end end } - def test_input_to! - config = { - key_mapping: { - "a" => "xx", - "ab" => "y", - "abc" => "z", - "x" => "rr" - } - } - stroke = Reline::KeyStroke.new(config) - result = ("abzwabk".bytes).map { |char| - stroke.input_to!(char)&.then { |result| - "#{result.as_s}" - } - } - assert_equal(result, [nil, nil, "yz", "w", nil, nil, "yk"]) - end - - def test_input_to + def test_match_status config = { key_mapping: { "a" => "xx", @@ -37,15 +23,15 @@ class Reline::KeyStroke::Test < Reline::TestCase } } stroke = Reline::KeyStroke.new(config) - assert_equal(stroke.input_to("a".bytes)&.as_s, nil) - assert_equal(stroke.input_to("ab".bytes)&.as_s, nil) - assert_equal(stroke.input_to("abc".bytes)&.as_s, "z") - assert_equal(stroke.input_to("abz".bytes)&.as_s, "yz") - assert_equal(stroke.input_to("abx".bytes)&.as_s, "yrr") - assert_equal(stroke.input_to("ac".bytes)&.as_s, "rrrrc") - assert_equal(stroke.input_to("aa".bytes)&.as_s, "rrrrrrrr") - assert_equal(stroke.input_to("x".bytes)&.as_s, "rr") - assert_equal(stroke.input_to("m".bytes)&.as_s, "m") - assert_equal(stroke.input_to("abzwabk".bytes)&.as_s, "yzwabk") + 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(:matched, stroke.match_status("abz".bytes)) + assert_equal(:matched, stroke.match_status("abx".bytes)) + assert_equal(:matched, stroke.match_status("ac".bytes)) + assert_equal(:matched, stroke.match_status("aa".bytes)) + assert_equal(:matched, stroke.match_status("x".bytes)) + assert_equal(:unmatched, stroke.match_status("m".bytes)) + assert_equal(:matched, stroke.match_status("abzwabk".bytes)) end end |