Skip to content

Commit f363a43

Browse files
authored
Always set ANSI CSI keybindings for Home, End, and Arrow. (#569)
1 parent 0924f2a commit f363a43

File tree

3 files changed

+57
-35
lines changed

3 files changed

+57
-35
lines changed

lib/reline/ansi.rb

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,21 @@ class Reline::ANSI
1414
'kcud1' => :ed_next_history,
1515
'kcuf1' => :ed_next_char,
1616
'kcub1' => :ed_prev_char,
17-
'cuu' => :ed_prev_history,
18-
'cud' => :ed_next_history,
19-
'cuf' => :ed_next_char,
20-
'cub' => :ed_prev_char,
17+
}
18+
19+
ANSI_CURSOR_KEY_BINDINGS = {
20+
# Up
21+
'A' => [:ed_prev_history, {}],
22+
# Down
23+
'B' => [:ed_next_history, {}],
24+
# Right
25+
'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
26+
# Left
27+
'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
28+
# End
29+
'F' => [:ed_move_to_end, {}],
30+
# Home
31+
'H' => [:ed_move_to_beg, {}],
2132
}
2233

2334
if Reline::Terminfo.enabled?
@@ -33,22 +44,12 @@ def self.win?
3344
end
3445

3546
def self.set_default_key_bindings(config, allow_terminfo: true)
47+
set_default_key_bindings_ansi_cursor(config)
3648
if allow_terminfo && Reline::Terminfo.enabled?
3749
set_default_key_bindings_terminfo(config)
3850
else
3951
set_default_key_bindings_comprehensive_list(config)
4052
end
41-
{
42-
# extended entries of terminfo
43-
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
44-
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
45-
[27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
46-
[27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
47-
}.each_pair do |key, func|
48-
config.add_default_key_binding_by_keymap(:emacs, key, func)
49-
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
50-
config.add_default_key_binding_by_keymap(:vi_command, key, func)
51-
end
5253
{
5354
[27, 91, 90] => :completion_journey_up, # S-Tab
5455
}.each_pair do |key, func|
@@ -64,18 +65,33 @@ def self.set_default_key_bindings(config, allow_terminfo: true)
6465
end
6566
end
6667

68+
def self.set_default_key_bindings_ansi_cursor(config)
69+
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
70+
bindings = [["\e[#{char}", default_func]] # CSI + char
71+
if modifiers[:ctrl]
72+
# CSI + ctrl_key_modifier + char
73+
bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
74+
end
75+
if modifiers[:meta]
76+
# CSI + meta_key_modifier + char
77+
bindings << ["\e[1;3#{char}", modifiers[:meta]]
78+
# Meta(ESC) + CSI + char
79+
bindings << ["\e\e[#{char}", modifiers[:meta]]
80+
end
81+
bindings.each do |sequence, func|
82+
key = sequence.bytes
83+
config.add_default_key_binding_by_keymap(:emacs, key, func)
84+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
85+
config.add_default_key_binding_by_keymap(:vi_command, key, func)
86+
end
87+
end
88+
end
89+
6790
def self.set_default_key_bindings_terminfo(config)
6891
key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
6992
begin
7093
key_code = Reline::Terminfo.tigetstr(capname)
71-
case capname
72-
# Escape sequences that omit the move distance and are set to defaults
73-
# value 1 may be sometimes sent by pressing the arrow-key.
74-
when 'cuu', 'cud', 'cuf', 'cub'
75-
[ key_code.sub(/%p1%d/, '').bytes, key_binding ]
76-
else
77-
[ key_code.bytes, key_binding ]
78-
end
94+
[ key_code.bytes, key_binding ]
7995
rescue Reline::Terminfo::TerminfoError
8096
# capname is undefined
8197
end
@@ -94,14 +110,8 @@ def self.set_default_key_bindings_comprehensive_list(config)
94110
[27, 91, 49, 126] => :ed_move_to_beg, # Home
95111
[27, 91, 52, 126] => :ed_move_to_end, # End
96112
[27, 91, 51, 126] => :key_delete, # Del
97-
[27, 91, 65] => :ed_prev_history, # ↑
98-
[27, 91, 66] => :ed_next_history, # ↓
99-
[27, 91, 67] => :ed_next_char, # →
100-
[27, 91, 68] => :ed_prev_char, # ←
101113

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

121-
# iTerm2
122-
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
123-
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
124-
[195, 166] => :em_next_word, # Option+f
125-
[195, 162] => :ed_prev_word, # Option+b
126-
127131
[27, 79, 65] => :ed_prev_history, # ↑
128132
[27, 79, 66] => :ed_next_history, # ↓
129133
[27, 79, 67] => :ed_next_char, # →

test/reline/test_ansi_with_terminfo.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,28 @@ def test_kcub1
7575
omit e.message
7676
end
7777

78+
# Home and End; always mapped regardless of terminfo enabled or not
79+
def test_home_end
80+
assert_key_binding("\e[H", :ed_move_to_beg)
81+
assert_key_binding("\e[F", :ed_move_to_end)
82+
end
83+
84+
# Arrow; always mapped regardless of terminfo enabled or not
85+
def test_arrow
86+
assert_key_binding("\e[A", :ed_prev_history)
87+
assert_key_binding("\e[B", :ed_next_history)
88+
assert_key_binding("\e[C", :ed_next_char)
89+
assert_key_binding("\e[D", :ed_prev_char)
90+
end
91+
7892
# Ctrl+arrow and Meta+arrow; always mapped regardless of terminfo enabled or not
7993
def test_extended
8094
assert_key_binding("\e[1;5C", :em_next_word) # Ctrl+→
8195
assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+←
8296
assert_key_binding("\e[1;3C", :em_next_word) # Meta+→
8397
assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+←
98+
assert_key_binding("\e\e[C", :em_next_word) # Meta+→
99+
assert_key_binding("\e\e[D", :ed_prev_word) # Meta+←
84100
end
85101

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

test/reline/test_ansi_without_terminfo.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def test_extended
6060
assert_key_binding("\e[1;5D", :ed_prev_word) # Ctrl+←
6161
assert_key_binding("\e[1;3C", :em_next_word) # Meta+→
6262
assert_key_binding("\e[1;3D", :ed_prev_word) # Meta+←
63+
assert_key_binding("\e\e[C", :em_next_word) # Meta+→
64+
assert_key_binding("\e\e[D", :ed_prev_word) # Meta+←
6365
end
6466

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

0 commit comments

Comments
 (0)