mirror of
https://github.com/ruby/ruby.git
synced 2026-01-27 04:24:23 +00:00
[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:
parent
631449ac6b
commit
91d4a7ae0c
@ -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
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
17
lib/reline/key_actor/composite.rb
Normal file
17
lib/reline/key_actor/composite.rb
Normal 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
|
||||
@ -1,5 +1,5 @@
|
||||
class Reline::KeyActor::Emacs < Reline::KeyActor::Base
|
||||
MAPPING = [
|
||||
module Reline::KeyActor
|
||||
EMACS_MAPPING = [
|
||||
# 0 ^@
|
||||
:em_set_mark,
|
||||
# 1 ^A
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
|
||||
MAPPING = [
|
||||
module Reline::KeyActor
|
||||
VI_COMMAND_MAPPING = [
|
||||
# 0 ^@
|
||||
:ed_unassigned,
|
||||
# 1 ^A
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
|
||||
MAPPING = [
|
||||
module Reline::KeyActor
|
||||
VI_INSERT_MAPPING = [
|
||||
# 0 ^@
|
||||
:ed_unassigned,
|
||||
# 1 ^A
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = '> '
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user