diff --git a/lib/reline.rb b/lib/reline.rb index 46856d1a51..5192733235 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -326,9 +326,12 @@ module Reline @@line_editor.pre_input_hook = @@pre_input_hook @@line_editor.rerender - config = Reline::IOGate::RAW_KEYSTROKE_CONFIG + @@config.reset_default_key_bindings + Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func| + @@config.add_default_key_binding(key, func) + end - key_stroke = Reline::KeyStroke.new(config) + key_stroke = Reline::KeyStroke.new(@@config) begin loop do key_stroke.read_io(@@config.keyseq_timeout) { |inputs| diff --git a/lib/reline/ansi.rb b/lib/reline/ansi.rb index e6b3344285..54ad3f2403 100644 --- a/lib/reline/ansi.rb +++ b/lib/reline/ansi.rb @@ -1,15 +1,13 @@ class Reline::ANSI RAW_KEYSTROKE_CONFIG = { - key_mapping: { - [27, 91, 65] => :ed_prev_history, # ↑ - [27, 91, 66] => :ed_next_history, # ↓ - [27, 91, 67] => :ed_next_char, # → - [27, 91, 68] => :ed_prev_char, # ← - [27, 91, 51, 126] => :key_delete, # Del - [27, 91, 49, 126] => :ed_move_to_beg, # Home - [27, 91, 52, 126] => :ed_move_to_end, # End - }.each_key(&:freeze).freeze, - }.freeze + [27, 91, 65] => :ed_prev_history, # ↑ + [27, 91, 66] => :ed_next_history, # ↓ + [27, 91, 67] => :ed_next_char, # → + [27, 91, 68] => :ed_prev_char, # ← + [27, 91, 51, 126] => :key_delete, # Del + [27, 91, 49, 126] => :ed_move_to_beg, # Home + [27, 91, 52, 126] => :ed_move_to_end, # End + }.each_key(&:freeze).freeze @@input = STDIN def self.input=(val) diff --git a/lib/reline/config.rb b/lib/reline/config.rb index 120a95a6fa..eddce36eba 100644 --- a/lib/reline/config.rb +++ b/lib/reline/config.rb @@ -36,6 +36,8 @@ class Reline::Config end def initialize + @additional_key_bindings = {} + @default_key_bindings = {} @skip_section = nil @if_stack = [] @editing_mode_label = :emacs @@ -52,6 +54,8 @@ class Reline::Config if editing_mode_is?(:vi_command) @editing_mode_label = :vi_insert end + @additional_key_bindings = {} + @default_key_bindings = {} end def editing_mode @@ -88,8 +92,23 @@ class Reline::Config self end + def key_bindings + # override @default_key_bindings with @additional_key_bindings + @default_key_bindings.merge(@additional_key_bindings) + end + + def add_default_key_binding(keystroke, target) + @default_key_bindings[keystroke] = target + end + + def reset_default_key_bindings + @default_key_bindings = {} + end + def read_lines(lines) lines.each do |line| + next if line.start_with?('#') + line = line.chomp.gsub(/^\s*/, '') if line[0, 1] == '$' handle_directive(line[1..-1]) @@ -106,7 +125,8 @@ class Reline::Config if line =~ /\s*(.*)\s*:\s*(.*)\s*$/ key, func_name = $1, $2 - bind_key(key, func_name) + keystroke, func = bind_key(key, func_name) + @additional_key_bindings[keystroke] = func end end end @@ -187,59 +207,57 @@ class Reline::Config def bind_key(key, func_name) if key =~ /"(.*)"/ - keyseq = parse_keyseq($1).force_encoding('ASCII-8BIT') + keyseq = parse_keyseq($1) else keyseq = nil end if func_name =~ /"(.*)"/ - func = parse_keyseq($1).force_encoding('ASCII-8BIT') + func = parse_keyseq($1) else - func = func_name.to_sym # It must be macro. + func = func_name.tr(?-, ?_).to_sym # It must be macro. end [keyseq, func] end - def key_notation_to_char(notation) + def key_notation_to_code(notation) case notation when /\\C-([A-Za-z_])/ - (1 + $1.downcase.ord - ?a.ord).chr('ASCII-8BIT') + (1 + $1.downcase.ord - ?a.ord) when /\\M-([0-9A-Za-z_])/ modified_key = $1 - code = - case $1 - when /[0-9]/ - ?\M-0.bytes.first + (modified_key.ord - ?0.ord) - when /[A-Z]/ - ?\M-A.bytes.first + (modified_key.ord - ?A.ord) - when /[a-z]/ - ?\M-a.bytes.first + (modified_key.ord - ?a.ord) - end - code.chr('ASCII-8BIT') + case $1 + when /[0-9]/ + ?\M-0.bytes.first + (modified_key.ord - ?0.ord) + when /[A-Z]/ + ?\M-A.bytes.first + (modified_key.ord - ?A.ord) + when /[a-z]/ + ?\M-a.bytes.first + (modified_key.ord - ?a.ord) + end when /\\C-M-[A-Za-z_]/, /\\M-C-[A-Za-z_]/ # 129 M-^A - when /\\(\d{1,3})/ then $1.to_i(8).chr # octal - when /\\x(\h{1,2})/ then $1.to_i(16).chr # hexadecimal - when "\\e" then ?\e - when "\\\\" then ?\\ - when "\\\"" then ?" - when "\\'" then ?' - when "\\a" then ?\a - when "\\b" then ?\b - when "\\d" then ?\d - when "\\f" then ?\f - when "\\n" then ?\n - when "\\r" then ?\r - when "\\t" then ?\t - when "\\v" then ?\v - else notation + when /\\(\d{1,3})/ then $1.to_i(8) # octal + when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal + when "\\e" then ?\e.ord + when "\\\\" then ?\\.ord + when "\\\"" then ?".ord + when "\\'" then ?'.ord + when "\\a" then ?\a.ord + when "\\b" then ?\b.ord + when "\\d" then ?\d.ord + when "\\f" then ?\f.ord + when "\\n" then ?\n.ord + when "\\r" then ?\r.ord + when "\\t" then ?\t.ord + when "\\v" then ?\v.ord + else notation.ord end end def parse_keyseq(str) # TODO: Control- and Meta- - ret = String.new(encoding: 'ASCII-8BIT') + ret = [] while str =~ /(\\C-[A-Za-z_]|\\M-[0-9A-Za-z_]|\\C-M-[A-Za-z_]|\\M-C-[A-Za-z_]|\\e|\\\\|\\"|\\'|\\a|\\b|\\d|\\f|\\n|\\r|\\t|\\v|\\\d{1,3}|\\x\h{1,2}|.)/ - ret << key_notation_to_char($&) + ret << key_notation_to_code($&) str = $' end ret diff --git a/lib/reline/general_io.rb b/lib/reline/general_io.rb index b015b951e6..d8e177f58c 100644 --- a/lib/reline/general_io.rb +++ b/lib/reline/general_io.rb @@ -1,7 +1,7 @@ require 'timeout' class Reline::GeneralIO - RAW_KEYSTROKE_CONFIG = {key_mapping: {}.freeze}.freeze + RAW_KEYSTROKE_CONFIG = {}.freeze @@buf = [] diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb index 64f3ed687d..08c00625ba 100644 --- a/lib/reline/key_stroke.rb +++ b/lib/reline/key_stroke.rb @@ -121,6 +121,6 @@ class Reline::KeyStroke end def key_mapping - @config[:key_mapping].transform_keys(&:bytes) + @config.key_bindings end end diff --git a/lib/reline/windows.rb b/lib/reline/windows.rb index 95c30c0246..007913dfbc 100644 --- a/lib/reline/windows.rb +++ b/lib/reline/windows.rb @@ -2,16 +2,14 @@ require 'fiddle/import' class Reline::Windows RAW_KEYSTROKE_CONFIG = { - key_mapping: { - [224, 72] => :ed_prev_history, # ↑ - [224, 80] => :ed_next_history, # ↓ - [224, 77] => :ed_next_char, # → - [224, 75] => :ed_prev_char, # ← - [224, 83] => :key_delete, # Del - [224, 71] => :ed_move_to_beg, # Home - [224, 79] => :ed_move_to_end, # End - }.each_key(&:freeze).freeze, - }.freeze + [224, 72] => :ed_prev_history, # ↑ + [224, 80] => :ed_next_history, # ↓ + [224, 77] => :ed_next_char, # → + [224, 75] => :ed_prev_char, # ← + [224, 83] => :key_delete, # Del + [224, 71] => :ed_move_to_beg, # Home + [224, 79] => :ed_move_to_end, # End + }.each_key(&:freeze).freeze class Win32API DLL = {} diff --git a/test/reline/test_config.rb b/test/reline/test_config.rb index bb06757901..6da639ca8d 100644 --- a/test/reline/test_config.rb +++ b/test/reline/test_config.rb @@ -28,42 +28,40 @@ class Reline::Config::Test < Reline::TestCase end def test_bind_key - key, func = @config.bind_key('"input"', '"abcde"') - - assert_equal 'input', key - assert_equal 'abcde', func + assert_equal ['input'.bytes, 'abcde'.bytes], @config.bind_key('"input"', '"abcde"') end def test_bind_key_with_macro - key, func = @config.bind_key('"input"', 'abcde') - assert_equal 'input', key - assert_equal :abcde, func + assert_equal ['input'.bytes, :abcde], @config.bind_key('"input"', 'abcde') end def test_bind_key_with_escaped_chars - assert_equal ['input', "\e \\ \" ' \a \b \d \f \n \r \t \v"], @config.bind_key('"input"', '"\\e \\\\ \\" \\\' \\a \\b \\d \\f \\n \\r \\t \\v"') + key, func = + assert_equal ['input'.bytes, "\e \\ \" ' \a \b \d \f \n \r \t \v".bytes], @config.bind_key('"input"', '"\\e \\\\ \\" \\\' \\a \\b \\d \\f \\n \\r \\t \\v"') end def test_bind_key_with_ctrl_chars - assert_equal ['input', "\C-h\C-h"], @config.bind_key('"input"', '"\C-h\C-H"') + assert_equal ['input'.bytes, "\C-h\C-h".bytes], @config.bind_key('"input"', '"\C-h\C-H"') end def test_bind_key_with_meta_chars - assert_equal ['input', "\M-h\M-H".force_encoding('ASCII-8BIT')], @config.bind_key('"input"', '"\M-h\M-H"') + assert_equal ['input'.bytes, "\M-h\M-H".bytes], @config.bind_key('"input"', '"\M-h\M-H"') end def test_bind_key_with_octal_number - assert_equal ['input', "\1"], @config.bind_key('"input"', '"\1"') - assert_equal ['input', "\12"], @config.bind_key('"input"', '"\12"') - assert_equal ['input', "\123"], @config.bind_key('"input"', '"\123"') - assert_equal ['input', ["\123", '4'].join], @config.bind_key('"input"', '"\1234"') + input = %w{i n p u t}.map(&:ord) + assert_equal [input, "\1".bytes], @config.bind_key('"input"', '"\1"') + assert_equal [input, "\12".bytes], @config.bind_key('"input"', '"\12"') + assert_equal [input, "\123".bytes], @config.bind_key('"input"', '"\123"') + assert_equal [input, "\123".bytes + '4'.bytes], @config.bind_key('"input"', '"\1234"') end def test_bind_key_with_hexadecimal_number - assert_equal ['input', "\x4"], @config.bind_key('"input"', '"\x4"') - assert_equal ['input', "\x45"], @config.bind_key('"input"', '"\x45"') - assert_equal ['input', ["\x45", '6'].join], @config.bind_key('"input"', '"\x456"') + input = %w{i n p u t}.map(&:ord) + assert_equal [input, "\x4".bytes], @config.bind_key('"input"', '"\x4"') + assert_equal [input, "\x45".bytes], @config.bind_key('"input"', '"\x45"') + assert_equal [input, "\x45".bytes + '6'.bytes], @config.bind_key('"input"', '"\x456"') end def test_include @@ -115,4 +113,15 @@ class Reline::Config::Test < Reline::TestCase assert_equal :audible, @config.instance_variable_get(:@bell_style) end + + def test_default_key_bindings + @config.add_default_key_binding('abcd'.bytes, 'EFGH'.bytes) + @config.read_lines(<<~'LINES'.split(/(?<=\n)/)) + "abcd": "ABCD" + "ijkl": "IJKL" + LINES + + expected = { 'abcd'.bytes => 'ABCD'.bytes, 'ijkl'.bytes => 'IJKL'.bytes } + assert_equal expected, @config.key_bindings + end end diff --git a/test/reline/test_key_stroke.rb b/test/reline/test_key_stroke.rb index e76129d04b..224e93d9a0 100644 --- a/test/reline/test_key_stroke.rb +++ b/test/reline/test_key_stroke.rb @@ -14,14 +14,15 @@ class Reline::KeyStroke::Test < Reline::TestCase } def test_match_status - config = { - key_mapping: { - "a" => "xx", - "ab" => "y", - "abc" => "z", - "x" => "rr" - } - } + config = Reline::Config.new + { + "a" => "xx", + "ab" => "y", + "abc" => "z", + "x" => "rr" + }.each_pair do |key, func| + 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))