diff options
-rw-r--r-- | lib/reline.rb | 96 | ||||
-rw-r--r-- | lib/reline/key_stroke.rb | 53 | ||||
-rw-r--r-- | lib/reline/line_editor.rb | 12 | ||||
-rw-r--r-- | test/reline/test_key_stroke.rb | 36 |
4 files changed, 84 insertions, 113 deletions
diff --git a/lib/reline.rb b/lib/reline.rb index 6bae469894..720df286a1 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -367,89 +367,39 @@ module Reline end end - # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC - # is followed by a character, and times out and treats it as a standalone - # ESC if the second character does not arrive. If the second character - # comes before timed out, it is treated as a modifier key with the - # meta-property of meta-key, so that it can be distinguished from - # multibyte characters with the 8th bit turned on. - # - # GNU Readline will wait for the 2nd character with "keyseq-timeout" - # milli-seconds but wait forever after 3rd characters. + # GNU Readline watis for "keyseq-timeout" milliseconds when the input is + # ambiguous whether it is matching or matched. + # If the next character does not arrive within the specified timeout, input + # is considered as matched. + # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of + # `ESC char` or part of CSI sequence (matching). private def read_io(keyseq_timeout, &block) buffer = [] + status = KeyStroke::MATCHING loop do - c = io_gate.getc(Float::INFINITY) - if c == -1 - result = :unmatched - else - buffer << c - result = 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) + timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY + c = io_gate.getc(timeout) + 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 - expanded, rest_bytes = key_stroke.expand(buffer) - rest_bytes.reverse_each { |c| io_gate.ungetc(c) } - block.(expanded) + status = KeyStroke::UNMATCHED end - break + else + buffer << c + status = key_stroke.match_status(buffer) end - end - end - private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block) - 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 + if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED expanded, rest_bytes = key_stroke.expand(buffer) rest_bytes.reverse_each { |c| io_gate.ungetc(c) } - block.(expanded) - return :break + block.call(expanded) + return 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 diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb index 419ddd8cea..ba40899685 100644 --- a/lib/reline/key_stroke.rb +++ b/lib/reline/key_stroke.rb @@ -7,17 +7,35 @@ class Reline::KeyStroke @config = config 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) - if key_mapping.matching?(input) - :matching - elsif key_mapping.get(input) - :matched + matching = key_mapping.matching?(input) + matched = key_mapping.get(input) + + # 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 match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command)) elsif input.size == 1 - :matched + MATCHED else - :unmatched + UNMATCHED end end @@ -25,7 +43,8 @@ class Reline::KeyStroke matched_bytes = nil (1..input.size).each do |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 return [[], []] unless matched_bytes @@ -50,13 +69,17 @@ class Reline::KeyStroke # returns match status of CSI/SS3 sequence and matched length def match_unknown_escape_sequence(input, vi_mode: false) idx = 0 - return :unmatched unless input[idx] == ESC_BYTE + return UNMATCHED unless input[idx] == ESC_BYTE idx += 1 idx += 1 if input[idx] == ESC_BYTE case input[idx] when nil - return :matching + if idx == 1 # `ESC` + return MATCHING_MATCHED + else # `ESC ESC` + return MATCHING + end when 91 # == '['.ord # CSI sequence `ESC [ ... char` idx += 1 @@ -67,9 +90,17 @@ class Reline::KeyStroke idx += 1 else # `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 - input[idx + 1] ? :unmatched : input[idx] ? :matched : :matching end def key_mapping diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index faa5b0194e..884b78ffdf 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -1081,17 +1081,7 @@ class Reline::LineEditor else # single byte 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 - 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) - end + process_key(key.combined_char, method_symbol) @multibyte_buffer.clear end if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb index de67c7ad44..ec70a05957 100644 --- a/test/reline/test_key_stroke.rb +++ b/test/reline/test_key_stroke.rb @@ -24,14 +24,14 @@ class Reline::KeyStroke::Test < Reline::TestCase config.add_default_key_binding(key.bytes, func.bytes) end stroke = Reline::KeyStroke.new(config) - 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(:unmatched, stroke.match_status("abz".bytes)) - assert_equal(:unmatched, stroke.match_status("abcx".bytes)) - assert_equal(:unmatched, stroke.match_status("aa".bytes)) - assert_equal(:matched, stroke.match_status("x".bytes)) - assert_equal(:unmatched, stroke.match_status("xa".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::MATCHED, stroke.match_status("abc".bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abz".bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("abcx".bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("aa".bytes)) + assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status("x".bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status("xa".bytes)) end def test_match_unknown @@ -50,10 +50,10 @@ class Reline::KeyStroke::Test < Reline::TestCase "\e\eX" ] sequences.each do |seq| - assert_equal(:matched, stroke.match_status(seq.bytes)) - assert_equal(:unmatched, stroke.match_status(seq.bytes + [32])) - (1...seq.size).each do |i| - assert_equal(:matching, stroke.match_status(seq.bytes.take(i))) + assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status(seq.bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status(seq.bytes + [32])) + (2...seq.size).each do |i| + assert_equal(Reline::KeyStroke::MATCHING, stroke.match_status(seq.bytes.take(i))) end end end @@ -84,8 +84,8 @@ class Reline::KeyStroke::Test < Reline::TestCase config.add_default_key_binding(key.bytes, func.bytes) end stroke = Reline::KeyStroke.new(config) - assert_equal(:unmatched, stroke.match_status('zzz'.bytes)) - assert_equal(:matched, stroke.match_status('abc'.bytes)) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('zzz'.bytes)) + assert_equal(Reline::KeyStroke::MATCHED, stroke.match_status('abc'.bytes)) end def test_with_reline_key @@ -97,9 +97,9 @@ class Reline::KeyStroke::Test < Reline::TestCase config.add_oneshot_key_binding(key, func.bytes) end stroke = Reline::KeyStroke.new(config) - assert_equal(:unmatched, stroke.match_status('da'.bytes)) - assert_equal(:matched, stroke.match_status("\eda".bytes)) - assert_equal(:unmatched, stroke.match_status([32, 195, 164])) - assert_equal(:matched, stroke.match_status([195, 164])) + assert_equal(Reline::KeyStroke::UNMATCHED, stroke.match_status('da'.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::MATCHED, stroke.match_status([195, 164])) end end |