mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	Use inputrc data for keystroke setting
This commit is contained in:
		
							parent
							
								
									c1e5299787
								
							
						
					
					
						commit
						7df65ef676
					
				
					 8 changed files with 109 additions and 82 deletions
				
			
		| 
						 | 
				
			
			@ -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|
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
require 'timeout'
 | 
			
		||||
 | 
			
		||||
class Reline::GeneralIO
 | 
			
		||||
  RAW_KEYSTROKE_CONFIG = {key_mapping: {}.freeze}.freeze
 | 
			
		||||
  RAW_KEYSTROKE_CONFIG = {}.freeze
 | 
			
		||||
 | 
			
		||||
  @@buf = []
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,6 +121,6 @@ class Reline::KeyStroke
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def key_mapping
 | 
			
		||||
    @config[:key_mapping].transform_keys(&:bytes)
 | 
			
		||||
    @config.key_bindings
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue