Skip to content

Commit

Permalink
Always set ANSI CSI keybindings for Home, End, and Arrow. (#569)
Browse files Browse the repository at this point in the history
  • Loading branch information
tompng authored Jul 19, 2023
1 parent 0924f2a commit f363a43
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 35 deletions.
74 changes: 39 additions & 35 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,21 @@ class Reline::ANSI
'kcud1' => :ed_next_history,
'kcuf1' => :ed_next_char,
'kcub1' => :ed_prev_char,
'cuu' => :ed_prev_history,
'cud' => :ed_next_history,
'cuf' => :ed_next_char,
'cub' => :ed_prev_char,
}

ANSI_CURSOR_KEY_BINDINGS = {
# Up
'A' => [:ed_prev_history, {}],
# Down
'B' => [:ed_next_history, {}],
# Right
'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
# Left
'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
# End
'F' => [:ed_move_to_end, {}],
# Home
'H' => [:ed_move_to_beg, {}],
}

if Reline::Terminfo.enabled?
Expand All @@ -33,22 +44,12 @@ def self.win?
end

def self.set_default_key_bindings(config, allow_terminfo: true)
set_default_key_bindings_ansi_cursor(config)
if allow_terminfo && Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
else
set_default_key_bindings_comprehensive_list(config)
end
{
# extended entries of terminfo
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
[27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
[27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
Expand All @@ -64,18 +65,33 @@ def self.set_default_key_bindings(config, allow_terminfo: true)
end
end

def self.set_default_key_bindings_ansi_cursor(config)
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
bindings = [["\e[#{char}", default_func]] # CSI + char
if modifiers[:ctrl]
# CSI + ctrl_key_modifier + char
bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
end
if modifiers[:meta]
# CSI + meta_key_modifier + char
bindings << ["\e[1;3#{char}", modifiers[:meta]]
# Meta(ESC) + CSI + char
bindings << ["\e\e[#{char}", modifiers[:meta]]
end
bindings.each do |sequence, func|
key = sequence.bytes
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end
end

def self.set_default_key_bindings_terminfo(config)
key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
begin
key_code = Reline::Terminfo.tigetstr(capname)
case capname
# Escape sequences that omit the move distance and are set to defaults
# value 1 may be sometimes sent by pressing the arrow-key.
when 'cuu', 'cud', 'cuf', 'cub'
[ key_code.sub(/%p1%d/, '').bytes, key_binding ]
else
[ key_code.bytes, key_binding ]
end
[ key_code.bytes, key_binding ]
rescue Reline::Terminfo::TerminfoError
# capname is undefined
end
Expand All @@ -94,14 +110,8 @@ def self.set_default_key_bindings_comprehensive_list(config)
[27, 91, 49, 126] => :ed_move_to_beg, # Home
[27, 91, 52, 126] => :ed_move_to_end, # End
[27, 91, 51, 126] => :key_delete, # Del
[27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char, # ←

# KDE
[27, 91, 72] => :ed_move_to_beg, # Home
[27, 91, 70] => :ed_move_to_end, # End
# Del is 0x08
[27, 71, 65] => :ed_prev_history, # ↑
[27, 71, 66] => :ed_next_history, # ↓
Expand All @@ -118,12 +128,6 @@ def self.set_default_key_bindings_comprehensive_list(config)
# Del is 0x08
# Arrow keys are the same of KDE

# iTerm2
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
[195, 166] => :em_next_word, # Option+f
[195, 162] => :ed_prev_word, # Option+b

[27, 79, 65] => :ed_prev_history, # ↑
[27, 79, 66] => :ed_next_history, # ↓
[27, 79, 67] => :ed_next_char, # →
Expand Down
16 changes: 16 additions & 0 deletions test/reline/test_ansi_with_terminfo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,28 @@ def test_kcub1
omit e.message
end

# Home and End; always mapped regardless of terminfo enabled or not
def test_home_end
assert_key_binding("\e[H", :ed_move_to_beg)
assert_key_binding("\e[F", :ed_move_to_end)
end

# Arrow; always mapped regardless of terminfo enabled or not
def test_arrow
assert_key_binding("\e[A", :ed_prev_history)
assert_key_binding("\e[B", :ed_next_history)
assert_key_binding("\e[C", :ed_next_char)
assert_key_binding("\e[D", :ed_prev_char)
end

# Ctrl+arrow and Meta+arrow; always mapped regardless of terminfo enabled or not
def test_extended
assert_key_binding("\e[1;5C", :em_next_word) # Ctrl+→
assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+←
assert_key_binding("\e[1;3C", :em_next_word) # Meta+→
assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+←
assert_key_binding("\e\e[C", :em_next_word) # Meta+→
assert_key_binding("\e\e[D", :ed_prev_word) # Meta+←
end

# Shift-Tab; always mapped regardless of terminfo enabled or not
Expand Down
2 changes: 2 additions & 0 deletions test/reline/test_ansi_without_terminfo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ def test_extended
assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+←
assert_key_binding("\e[1;3C", :em_next_word) # Meta+→
assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+←
assert_key_binding("\e\e[C", :em_next_word) # Meta+→
assert_key_binding("\e\e[D", :ed_prev_word) # Meta+←
end

# Shift-Tab; always mapped regardless of terminfo enabled or not
Expand Down

0 comments on commit f363a43

Please sign in to comment.