[ruby/reline] Improve key binding match/matching check

(https://github.com/ruby/reline/pull/709)

* Improve key binding match/matching check

* Rename key_actors to default_key_bindings

* Make key_stroke.expand always return a value

* Update add_default_key_binding to use a add_default_key_binding_by_keymap internally

Co-authored-by: Stan Lo <stan001212@gmail.com>

---------

https://github.com/ruby/reline/commit/353ec236e2

Co-authored-by: Stan Lo <stan001212@gmail.com>
This commit is contained in:
tomoya ishida 2024-06-03 22:14:57 +09:00 committed by git
parent 631449ac6b
commit 91d4a7ae0c
17 changed files with 201 additions and 277 deletions

View File

@ -19,20 +19,10 @@ module Reline
class ConfigEncodingConversionError < StandardError; end
Key = Struct.new(:char, :combined_char, :with_meta) do
def match?(other)
case other
when Reline::Key
(other.char.nil? or char.nil? or char == other.char) and
(other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
(other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
when Integer, Symbol
(combined_char and combined_char == other) or
(combined_char.nil? and char and char == other)
else
false
end
# For dialog_proc `key.match?(dialog.name)`
def match?(sym)
combined_char.is_a?(Symbol) && combined_char == sym
end
alias_method :==, :match?
end
CursorPos = Struct.new(:x, :y)
DialogRenderInfo = Struct.new(
@ -400,9 +390,8 @@ module Reline
end
case result
when :matched
expanded = key_stroke.expand(buffer).map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
expanded, rest_bytes = key_stroke.expand(buffer)
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
block.(expanded)
break
when :matching
@ -416,9 +405,8 @@ module Reline
if buffer.size == 1 and c == "\e".ord
read_escaped_key(keyseq_timeout, c, block)
else
expanded = buffer.map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
expanded, rest_bytes = key_stroke.expand(buffer)
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
block.(expanded)
end
break
@ -442,9 +430,8 @@ module Reline
return :next
when :matched
buffer << succ_c
expanded = key_stroke.expand(buffer).map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
expanded, rest_bytes = key_stroke.expand(buffer)
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
block.(expanded)
return :break
end

View File

@ -29,18 +29,20 @@ class Reline::Config
attr_accessor :autocompletion
def initialize
@additional_key_bindings = {} # from inputrc
@additional_key_bindings[:emacs] = {}
@additional_key_bindings[:vi_insert] = {}
@additional_key_bindings[:vi_command] = {}
@oneshot_key_bindings = {}
@additional_key_bindings = { # from inputrc
emacs: Reline::KeyActor::Base.new,
vi_insert: Reline::KeyActor::Base.new,
vi_command: Reline::KeyActor::Base.new
}
@oneshot_key_bindings = Reline::KeyActor::Base.new
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
@key_actors = {}
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
@default_key_bindings = {
emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING),
vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING),
vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING)
}
@vi_cmd_mode_string = '(cmd)'
@vi_ins_mode_string = '(ins)'
@emacs_mode_string = '@'
@ -62,7 +64,7 @@ class Reline::Config
end
def editing_mode
@key_actors[@editing_mode_label]
@default_key_bindings[@editing_mode_label]
end
def editing_mode=(val)
@ -74,7 +76,7 @@ class Reline::Config
end
def keymap
@key_actors[@keymap_label]
@default_key_bindings[@keymap_label]
end
def loaded?
@ -133,14 +135,14 @@ class Reline::Config
def key_bindings
# The key bindings for each editing mode will be overwritten by the user-defined ones.
kb = @key_actors[@editing_mode_label].default_key_bindings.dup
kb.merge!(@additional_key_bindings[@editing_mode_label])
kb.merge!(@oneshot_key_bindings)
kb
Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]])
end
def add_oneshot_key_binding(keystroke, target)
@oneshot_key_bindings[keystroke] = target
# IRB sets invalid keystroke [Reline::Key]. We should ignore it.
return unless keystroke.all? { |c| c.is_a?(Integer) }
@oneshot_key_bindings.add(keystroke, target)
end
def reset_oneshot_key_bindings
@ -148,11 +150,11 @@ class Reline::Config
end
def add_default_key_binding_by_keymap(keymap, keystroke, target)
@key_actors[keymap].default_key_bindings[keystroke] = target
@default_key_bindings[keymap].add(keystroke, target)
end
def add_default_key_binding(keystroke, target)
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
add_default_key_binding_by_keymap(@keymap_label, keystroke, target)
end
def read_lines(lines, file = nil)
@ -192,7 +194,7 @@ class Reline::Config
func_name = func_name.split.first
keystroke, func = bind_key(key, func_name)
next unless keystroke
@additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
@additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func)
end
end
unless if_stack.empty?

View File

@ -2,6 +2,7 @@ module Reline::KeyActor
end
require 'reline/key_actor/base'
require 'reline/key_actor/composite'
require 'reline/key_actor/emacs'
require 'reline/key_actor/vi_command'
require 'reline/key_actor/vi_insert'

View File

@ -1,15 +1,31 @@
class Reline::KeyActor::Base
MAPPING = Array.new(256)
def initialize(mapping = [])
@mapping = mapping
@matching_bytes = {}
@key_bindings = {}
end
def get_method(key)
self.class::MAPPING[key]
@mapping[key]
end
def initialize
@default_key_bindings = {}
def add(key, func)
(1...key.size).each do |size|
@matching_bytes[key.take(size)] = true
end
@key_bindings[key] = func
end
def default_key_bindings
@default_key_bindings
def matching?(key)
@matching_bytes[key]
end
def get(key)
@key_bindings[key]
end
def clear
@matching_bytes.clear
@key_bindings.clear
end
end

View File

@ -0,0 +1,17 @@
class Reline::KeyActor::Composite
def initialize(key_actors)
@key_actors = key_actors
end
def matching?(key)
@key_actors.any? { |key_actor| key_actor.matching?(key) }
end
def get(key)
@key_actors.each do |key_actor|
func = key_actor.get(key)
return func if func
end
nil
end
end

View File

@ -1,5 +1,5 @@
class Reline::KeyActor::Emacs < Reline::KeyActor::Base
MAPPING = [
module Reline::KeyActor
EMACS_MAPPING = [
# 0 ^@
:em_set_mark,
# 1 ^A

View File

@ -1,5 +1,5 @@
class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
MAPPING = [
module Reline::KeyActor
VI_COMMAND_MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A

View File

@ -1,5 +1,5 @@
class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
MAPPING = [
module Reline::KeyActor
VI_INSERT_MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A

View File

@ -7,139 +7,69 @@ class Reline::KeyStroke
@config = config
end
def compress_meta_key(ary)
return ary unless @config.convert_meta
ary.inject([]) { |result, key|
if result.size > 0 and result.last == "\e".ord
result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
else
result << key
end
result
}
end
def start_with?(me, other)
compressed_me = compress_meta_key(me)
compressed_other = compress_meta_key(other)
i = 0
loop do
my_c = compressed_me[i]
other_c = compressed_other[i]
other_is_last = (i + 1) == compressed_other.size
me_is_last = (i + 1) == compressed_me.size
if my_c != other_c
if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
return true
else
return false
end
elsif other_is_last
return true
elsif me_is_last
return false
end
i += 1
end
end
def equal?(me, other)
case me
when Array
compressed_me = compress_meta_key(me)
compressed_other = compress_meta_key(other)
compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| equal?(i[0], i[1]) }
when Integer
if other.is_a?(Reline::Key)
if other.combined_char == "\e".ord
false
else
other.combined_char == me
end
else
me == other
end
when Reline::Key
if other.is_a?(Integer)
me.combined_char == other
else
me == other
end
end
end
def match_status(input)
key_mapping.keys.select { |lhs|
start_with?(lhs, input)
}.tap { |it|
return :matched if it.size == 1 && equal?(it[0], input)
return :matching if it.size == 1 && !equal?(it[0], input)
return :matched if it.max_by(&:size)&.size&.< input.size
return :matching if it.size > 1
}
if key_mapping.keys.any? { |lhs| start_with?(input, lhs) }
if key_mapping.matching?(input)
:matching
elsif key_mapping.get(input)
: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
else
match_unknown_escape_sequence(input).first
:unmatched
end
end
def expand(input)
lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
unless lhs
status, size = match_unknown_escape_sequence(input)
case status
when :matched
return [:ed_unassigned] + expand(input.drop(size))
when :matching
return [:ed_unassigned]
else
return input
end
matched_bytes = nil
(1..input.size).each do |i|
bytes = input.take(i)
matched_bytes = bytes if match_status(bytes) != :unmatched
end
rhs = key_mapping[lhs]
return [[], []] unless matched_bytes
case rhs
when String
rhs_bytes = rhs.bytes
expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
when Symbol
[rhs] + expand(input.drop(lhs.size))
when Array
rhs
func = key_mapping.get(matched_bytes)
if func.is_a?(Array)
keys = func.map { |c| Reline::Key.new(c, c, false) }
elsif func
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
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
else
keys = []
end
[keys, input.drop(matched_bytes.size)]
end
private
# returns match status of CSI/SS3 sequence and matched length
def match_unknown_escape_sequence(input)
def match_unknown_escape_sequence(input, vi_mode: false)
idx = 0
return [:unmatched, nil] 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, nil]
return :matching
when 91 # == '['.ord
# CSI sequence
# CSI sequence `ESC [ ... char`
idx += 1
idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
input[idx] ? [:matched, idx + 1] : [:matching, nil]
when 79 # == 'O'.ord
# SS3 sequence
input[idx + 1] ? [:matched, idx + 2] : [:matching, nil]
# SS3 sequence `ESC O char`
idx += 1
else
if idx == 1
# `ESC char`, make it :unmatched so that it will be handled correctly in `read_2nd_character_of_key_sequence`
[:unmatched, nil]
else
# `ESC ESC char`
[:matched, idx + 1]
end
# `ESC char` or `ESC ESC char`
return :unmatched if vi_mode
end
input[idx + 1] ? :unmatched : input[idx] ? :matched : :matching
end
def key_mapping

View File

@ -684,10 +684,8 @@ class Reline::LineEditor
@trap_key.each do |t|
@config.add_oneshot_key_binding(t, @name)
end
elsif @trap_key.is_a?(Array)
else
@config.add_oneshot_key_binding(@trap_key, @name)
elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
@config.add_oneshot_key_binding([@trap_key], @name)
end
end
dialog_render_info

View File

@ -168,7 +168,7 @@ class Reline::TestCase < Test::Unit::TestCase
def assert_key_binding(input, method_symbol, editing_modes = [:emacs, :vi_insert, :vi_command])
editing_modes.each do |editing_mode|
@config.editing_mode = editing_mode
assert_equal(method_symbol, @config.editing_mode.default_key_bindings[input.bytes])
assert_equal(method_symbol, @config.editing_mode.get(input.bytes))
end
end
end

View File

@ -22,6 +22,15 @@ class Reline::Config::Test < Reline::TestCase
@config.reset
end
def additional_key_bindings(keymap_label)
@config.instance_variable_get(:@additional_key_bindings)[keymap_label].instance_variable_get(:@key_bindings)
end
def registered_key_bindings(keys)
key_bindings = @config.key_bindings
keys.to_h { |key| [key, key_bindings.get(key)] }
end
def test_read_lines
@config.read_lines(<<~LINES.lines)
set bell-style on
@ -97,14 +106,17 @@ class Reline::Config::Test < Reline::TestCase
assert_equal nil, @config.convert_meta
end
def test_comment_line
@config.read_lines([" #a: error\n"])
assert_not_include @config.key_bindings, nil
end
def test_invalid_keystroke
@config.read_lines(["a: error\n"])
assert_not_include @config.key_bindings, nil
@config.read_lines(<<~LINES.lines)
#"a": comment
a: error
"b": no-error
LINES
key_bindings = additional_key_bindings(:emacs)
assert_not_include key_bindings, 'a'.bytes
assert_not_include key_bindings, nil
assert_not_include key_bindings, []
assert_include key_bindings, 'b'.bytes
end
def test_bind_key
@ -242,8 +254,8 @@ class Reline::Config::Test < Reline::TestCase
"\xC": "O"
LINES
keys = [0x1, 0x3, 0x4, 0x6, 0x7, 0xC]
key_bindings = keys.to_h { |k| [[k.ord], ['O'.ord]] }
assert_equal(key_bindings, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
key_bindings = keys.to_h { |k| [[k], ['O'.ord]] }
assert_equal(key_bindings, additional_key_bindings(:emacs))
end
def test_unclosed_if
@ -282,9 +294,9 @@ class Reline::Config::Test < Reline::TestCase
$endif
LINES
assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
assert_equal({[5] => :history_search_backward}, additional_key_bindings(:emacs))
assert_equal({}, additional_key_bindings(:vi_insert))
assert_equal({}, additional_key_bindings(:vi_command))
end
def test_else
@ -296,9 +308,9 @@ class Reline::Config::Test < Reline::TestCase
$endif
LINES
assert_equal({[6] => :history_search_forward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
assert_equal({[6] => :history_search_forward}, additional_key_bindings(:emacs))
assert_equal({}, additional_key_bindings(:vi_insert))
assert_equal({}, additional_key_bindings(:vi_command))
end
def test_if_with_invalid_mode
@ -310,9 +322,9 @@ class Reline::Config::Test < Reline::TestCase
$endif
LINES
assert_equal({[6] => :history_search_forward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
assert_equal({[6] => :history_search_forward}, additional_key_bindings(:emacs))
assert_equal({}, additional_key_bindings(:vi_insert))
assert_equal({}, additional_key_bindings(:vi_command))
end
def test_mode_label_differs_from_keymap_label
@ -327,9 +339,9 @@ class Reline::Config::Test < Reline::TestCase
"\C-e": history-search-backward
$endif
LINES
assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
assert_equal({[5] => :history_search_backward}, additional_key_bindings(:emacs))
assert_equal({}, additional_key_bindings(:vi_insert))
assert_equal({}, additional_key_bindings(:vi_command))
end
def test_if_without_else_condition
@ -340,9 +352,9 @@ class Reline::Config::Test < Reline::TestCase
$endif
LINES
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:emacs])
assert_equal({[5] => :history_search_backward}, @config.instance_variable_get(:@additional_key_bindings)[:vi_insert])
assert_equal({}, @config.instance_variable_get(:@additional_key_bindings)[:vi_command])
assert_equal({}, additional_key_bindings(:emacs))
assert_equal({[5] => :history_search_backward}, additional_key_bindings(:vi_insert))
assert_equal({}, additional_key_bindings(:vi_command))
end
def test_default_key_bindings
@ -353,7 +365,7 @@ class Reline::Config::Test < Reline::TestCase
LINES
expected = { 'abcd'.bytes => 'ABCD'.bytes, 'ijkl'.bytes => 'IJKL'.bytes }
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_additional_key_bindings
@ -363,7 +375,7 @@ class Reline::Config::Test < Reline::TestCase
LINES
expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes }
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_additional_key_bindings_with_nesting_and_comment_out
@ -375,7 +387,7 @@ class Reline::Config::Test < Reline::TestCase
LINES
expected = { 'ef'.bytes => 'EF'.bytes, 'gh'.bytes => 'GH'.bytes }
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_additional_key_bindings_for_other_keymap
@ -390,7 +402,7 @@ class Reline::Config::Test < Reline::TestCase
LINES
expected = { 'cd'.bytes => 'CD'.bytes }
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_additional_key_bindings_for_auxiliary_emacs_keymaps
@ -412,7 +424,7 @@ class Reline::Config::Test < Reline::TestCase
"\C-xef".bytes => 'EF'.bytes,
"\egh".bytes => 'GH'.bytes,
}
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_key_bindings_with_reset
@ -424,7 +436,7 @@ class Reline::Config::Test < Reline::TestCase
LINES
@config.reset
expected = { 'default'.bytes => 'DEFAULT'.bytes, 'additional'.bytes => 'ADDITIONAL'.bytes }
assert_equal expected, @config.key_bindings
assert_equal expected, registered_key_bindings(expected.keys)
end
def test_history_size

View File

@ -1,6 +1,6 @@
require_relative 'helper'
class Reline::KeyActor::Emacs::Test < Reline::TestCase
class Reline::KeyActor::EmacsTest < Reline::TestCase
def setup
Reline.send(:test_mode)
@prompt = '> '

View File

@ -1,6 +1,6 @@
require_relative 'helper'
class Reline::KeyActor::ViInsert::Test < Reline::TestCase
class Reline::ViInsertTest < Reline::TestCase
def setup
Reline.send(:test_mode)
@prompt = '> '
@ -13,69 +13,73 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
@line_editor.reset(@prompt, encoding: @encoding)
end
def editing_mode_label
@config.instance_variable_get(:@editing_mode_label)
end
def teardown
Reline.test_reset
end
def test_vi_command_mode
input_keys("\C-[")
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
end
def test_vi_command_mode_with_input
input_keys("abc\C-[")
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
assert_line_around_cursor('ab', 'c')
end
def test_vi_insert
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys('i')
assert_line_around_cursor('i', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys("\C-[")
assert_line_around_cursor('', 'i')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
input_keys('i')
assert_line_around_cursor('', 'i')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
end
def test_vi_add
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys('a')
assert_line_around_cursor('a', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys("\C-[")
assert_line_around_cursor('', 'a')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
input_keys('a')
assert_line_around_cursor('a', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
end
def test_vi_insert_at_bol
input_keys('I')
assert_line_around_cursor('I', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys("12345\C-[hh")
assert_line_around_cursor('I12', '345')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
input_keys('I')
assert_line_around_cursor('', 'I12345')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
end
def test_vi_add_at_eol
input_keys('A')
assert_line_around_cursor('A', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
input_keys("12345\C-[hh")
assert_line_around_cursor('A12', '345')
assert_instance_of(Reline::KeyActor::ViCommand, @config.editing_mode)
assert_equal(:vi_command, editing_mode_label)
input_keys('A')
assert_line_around_cursor('A12345', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
end
def test_ed_insert_one
@ -901,11 +905,11 @@ class Reline::KeyActor::ViInsert::Test < Reline::TestCase
assert_line_around_cursor('abc', '')
input_keys("\C-[0C")
assert_line_around_cursor('', '')
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
end
def test_vi_motion_operators
assert_instance_of(Reline::KeyActor::ViInsert, @config.editing_mode)
assert_equal(:vi_insert, editing_mode_label)
assert_nothing_raised do
input_keys("test = { foo: bar }\C-[BBBldt}b")

View File

@ -27,13 +27,11 @@ class Reline::KeyStroke::Test < Reline::TestCase
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(: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("m".bytes))
assert_equal(:matched, stroke.match_status("abzwabk".bytes))
assert_equal(:unmatched, stroke.match_status("xa".bytes))
end
def test_match_unknown
@ -47,10 +45,13 @@ class Reline::KeyStroke::Test < Reline::TestCase
"\e[1;1R", # Cursor position report
"\e[15~", # F5
"\eOP", # F1
"\e\e[A" # Option+Up
"\e\e[A", # Option+Up
"\eX",
"\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)))
end
@ -61,16 +62,18 @@ class Reline::KeyStroke::Test < Reline::TestCase
config = Reline::Config.new
{
'abc' => '123',
'ab' => '456'
}.each_pair do |key, func|
config.add_default_key_binding(key.bytes, func.bytes)
end
stroke = Reline::KeyStroke.new(config)
assert_equal('123'.bytes, stroke.expand('abc'.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))
# CSI sequence
assert_equal([:ed_unassigned] + 'bc'.bytes, stroke.expand("\e[1;2;3;4;5abc".bytes))
assert_equal([:ed_unassigned] + 'BC'.bytes, stroke.expand("\e\e[ABC".bytes))
assert_equal([[], 'bc'.bytes], stroke.expand("\e[1;2;3;4;5abc".bytes))
assert_equal([[], 'BC'.bytes], stroke.expand("\e\e[ABC".bytes))
# SS3 sequence
assert_equal([:ed_unassigned] + 'QR'.bytes, stroke.expand("\eOPQR".bytes))
assert_equal([[], 'QR'.bytes], stroke.expand("\eOPQR".bytes))
end
def test_oneshot_key_bindings
@ -88,17 +91,14 @@ class Reline::KeyStroke::Test < Reline::TestCase
def test_with_reline_key
config = Reline::Config.new
{
[
Reline::Key.new(100, 228, true), # Alt+d
Reline::Key.new(97, 97, false) # a
] => 'abc',
"\eda".bytes => 'abc', # Alt+d a
[195, 164] => 'def'
}.each_pair do |key, func|
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("\M-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]))
end

View File

@ -303,12 +303,12 @@ class Reline::Test < Reline::TestCase
def test_vi_editing_mode
Reline.vi_editing_mode
assert_equal(Reline::KeyActor::ViInsert, Reline.core.config.editing_mode.class)
assert_equal(:vi_insert, Reline.core.config.instance_variable_get(:@editing_mode_label))
end
def test_emacs_editing_mode
Reline.emacs_editing_mode
assert_equal(Reline::KeyActor::Emacs, Reline.core.config.editing_mode.class)
assert_equal(:emacs, Reline.core.config.instance_variable_get(:@editing_mode_label))
end
def test_add_dialog_proc

View File

@ -2,53 +2,10 @@ require_relative 'helper'
require "reline"
class Reline::TestKey < Reline::TestCase
def setup
Reline.test_mode
end
def teardown
Reline.test_reset
end
def test_match_key
assert(Reline::Key.new(1, 2, false).match?(Reline::Key.new(1, 2, false)))
assert(Reline::Key.new(1, 2, false).match?(Reline::Key.new(nil, 2, false)))
assert(Reline::Key.new(1, 2, false).match?(Reline::Key.new(1, 2, nil)))
assert(Reline::Key.new(nil, 2, false).match?(Reline::Key.new(nil, 2, false)))
assert(Reline::Key.new(1, nil, false).match?(Reline::Key.new(1, nil, false)))
assert(Reline::Key.new(1, 2, nil).match?(Reline::Key.new(1, 2, nil)))
assert(Reline::Key.new(nil, 2, false).match?(Reline::Key.new(nil, 2, false)))
assert(Reline::Key.new(1, nil, false).match?(Reline::Key.new(1, nil, false)))
assert(Reline::Key.new(1, 2, nil).match?(Reline::Key.new(1, 2, nil)))
assert(!Reline::Key.new(1, 2, false).match?(Reline::Key.new(3, 1, false)))
assert(!Reline::Key.new(1, 2, false).match?(Reline::Key.new(1, 3, false)))
assert(!Reline::Key.new(1, 2, false).match?(Reline::Key.new(1, 3, true)))
end
def test_match_integer
assert(Reline::Key.new(1, 2, false).match?(2))
assert(Reline::Key.new(nil, 2, false).match?(2))
assert(Reline::Key.new(1, nil, false).match?(1))
assert(!Reline::Key.new(1, 2, false).match?(1))
assert(!Reline::Key.new(1, nil, false).match?(2))
assert(!Reline::Key.new(nil, nil, false).match?(1))
end
def test_match_symbol
assert(Reline::Key.new(:key1, :key2, false).match?(:key2))
assert(Reline::Key.new(:key1, nil, false).match?(:key1))
assert(!Reline::Key.new(:key1, :key2, false).match?(:key1))
assert(!Reline::Key.new(:key1, nil, false).match?(:key2))
assert(!Reline::Key.new(nil, nil, false).match?(:key1))
end
def test_match_other
assert(!Reline::Key.new(:key1, 2, false).match?("key1"))
assert(!Reline::Key.new(nil, nil, false).match?(nil))
assert(Reline::Key.new(:key1, :key1, false).match?(:key1))
refute(Reline::Key.new(:key1, :key1, false).match?(:key2))
refute(Reline::Key.new(:key1, :key1, false).match?(nil))
refute(Reline::Key.new(1, 1, false).match?(:key1))
end
end