Skip to content

Commit 329f370

Browse files
allow more key configuration, prevent doublebinds
1 parent 81c2197 commit 329f370

File tree

5 files changed

+78
-37
lines changed

5 files changed

+78
-37
lines changed

bpython/config.py

Lines changed: 55 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def loadini(struct, configfile):
3535
config_path = os.path.expanduser(configfile)
3636

3737
config = ConfigParser()
38-
fill_config_with_default_values(config, {
38+
defaults = {
3939
'general': {
4040
'arg_spec': True,
4141
'auto_display_list': True,
@@ -61,8 +61,15 @@ def loadini(struct, configfile):
6161
'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
6262
},
6363
'keyboard': {
64+
'backspace': 'C-h',
65+
'left': 'C-b',
66+
'right': 'C-f',
67+
'beginning_of_line': 'C-a',
68+
'end_of_line': 'C-e',
69+
'transpose_chars': 'C-t',
6470
'clear_line': 'C-u',
6571
'clear_screen': 'C-l',
72+
'kill_line': 'C-k',
6673
'clear_word': 'C-w',
6774
'cut_to_buffer': 'C-k',
6875
'delete': 'C-d',
@@ -90,7 +97,8 @@ def loadini(struct, configfile):
9097
'curtsies': {
9198
'list_above' : False,
9299
'right_arrow_completion' : True,
93-
}})
100+
}}
101+
fill_config_with_default_values(config, defaults)
94102
if not config.read(config_path):
95103
# No config file. If the user has it in the old place then complain
96104
if os.path.isfile(os.path.expanduser('~/.bpython.ini')):
@@ -99,6 +107,18 @@ def loadini(struct, configfile):
99107
"%s\n" % default_config_path())
100108
sys.exit(1)
101109

110+
def get_key_no_doublebind(attr, already_used={}):
111+
"""Clears any other configured keybindings using this key"""
112+
key = config.get('keyboard', attr)
113+
if key in already_used:
114+
default = defaults['keyboard'][already_used[key]]
115+
if default in already_used:
116+
setattr(struct, '%s_key' % already_used[key], '')
117+
else:
118+
setattr(struct, '%s_key' % already_used[key], default)
119+
already_used[key] = attr
120+
return key
121+
102122
struct.config_path = config_path
103123

104124
struct.dedent_after = config.getint('general', 'dedent_after')
@@ -115,28 +135,39 @@ def loadini(struct, configfile):
115135
struct.hist_length = config.getint('general', 'hist_length')
116136
struct.hist_duplicates = config.getboolean('general', 'hist_duplicates')
117137
struct.flush_output = config.getboolean('general', 'flush_output')
118-
struct.pastebin_key = config.get('keyboard', 'pastebin')
119-
struct.save_key = config.get('keyboard', 'save')
120-
struct.search_key = config.get('keyboard', 'search')
121-
struct.show_source_key = config.get('keyboard', 'show_source')
122-
struct.suspend_key = config.get('keyboard', 'suspend')
123-
struct.toggle_file_watch_key = config.get('keyboard', 'toggle_file_watch')
124-
struct.undo_key = config.get('keyboard', 'undo')
125-
struct.reimport_key = config.get('keyboard', 'reimport')
126-
struct.up_one_line_key = config.get('keyboard', 'up_one_line')
127-
struct.down_one_line_key = config.get('keyboard', 'down_one_line')
128-
struct.cut_to_buffer_key = config.get('keyboard', 'cut_to_buffer')
129-
struct.yank_from_buffer_key = config.get('keyboard', 'yank_from_buffer')
130-
struct.clear_word_key = config.get('keyboard', 'clear_word')
131-
struct.clear_line_key = config.get('keyboard', 'clear_line')
132-
struct.clear_screen_key = config.get('keyboard', 'clear_screen')
133-
struct.delete_key = config.get('keyboard', 'delete')
134-
struct.exit_key = config.get('keyboard', 'exit')
135-
struct.last_output_key = config.get('keyboard', 'last_output')
136-
struct.edit_config_key = config.get('keyboard', 'edit_config')
137-
struct.edit_current_block_key = config.get('keyboard', 'edit_current_block')
138-
struct.external_editor_key = config.get('keyboard', 'external_editor')
139-
struct.help_key = config.get('keyboard', 'help')
138+
139+
struct.pastebin_key = get_key_no_doublebind('pastebin')
140+
struct.save_key = get_key_no_doublebind('save')
141+
struct.search_key = get_key_no_doublebind('search')
142+
struct.show_source_key = get_key_no_doublebind('show_source')
143+
struct.suspend_key = get_key_no_doublebind('suspend')
144+
struct.toggle_file_watch_key = get_key_no_doublebind('toggle_file_watch')
145+
struct.undo_key = get_key_no_doublebind('undo')
146+
struct.reimport_key = get_key_no_doublebind('reimport')
147+
struct.up_one_line_key = get_key_no_doublebind('up_one_line')
148+
struct.down_one_line_key = get_key_no_doublebind('down_one_line')
149+
struct.cut_to_buffer_key = get_key_no_doublebind('cut_to_buffer')
150+
struct.yank_from_buffer_key = get_key_no_doublebind('yank_from_buffer')
151+
struct.clear_word_key = get_key_no_doublebind('clear_word')
152+
struct.backspace_key = get_key_no_doublebind('backspace')
153+
struct.clear_line_key = get_key_no_doublebind('clear_line')
154+
struct.clear_screen_key = get_key_no_doublebind('clear_screen')
155+
struct.delete_key = get_key_no_doublebind('delete')
156+
157+
struct.left_key = get_key_no_doublebind('left')
158+
struct.right_key = get_key_no_doublebind('right')
159+
struct.end_of_line_key = get_key_no_doublebind('end_of_line')
160+
struct.beginning_of_line_key = get_key_no_doublebind('beginning_of_line')
161+
struct.transpose_chars_key = get_key_no_doublebind('transpose_chars')
162+
struct.clear_line_key = get_key_no_doublebind('clear_line')
163+
struct.clear_screen_key = get_key_no_doublebind('clear_screen')
164+
struct.kill_line_key = get_key_no_doublebind('kill_line')
165+
struct.exit_key = get_key_no_doublebind('exit')
166+
struct.last_output_key = get_key_no_doublebind('last_output')
167+
struct.edit_config_key = get_key_no_doublebind('edit_config')
168+
struct.edit_current_block_key = get_key_no_doublebind('edit_current_block')
169+
struct.external_editor_key = get_key_no_doublebind('external_editor')
170+
struct.help_key = get_key_no_doublebind('help')
140171

141172
struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm')
142173
struct.pastebin_url = config.get('general', 'pastebin_url')

bpython/curtsiesfrontend/manual_readline.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,22 +141,22 @@ def kills_ahead(func):
141141
func.kills = 'ahead'
142142
return func
143143

144-
@edit_keys.on('<Ctrl-b>')
144+
@edit_keys.on(config='left_key')
145145
@edit_keys.on('<LEFT>')
146146
def left_arrow(cursor_offset, line):
147147
return max(0, cursor_offset - 1), line
148148

149-
@edit_keys.on('<Ctrl-f>')
149+
@edit_keys.on(config='right_key')
150150
@edit_keys.on('<RIGHT>')
151151
def right_arrow(cursor_offset, line):
152152
return min(len(line), cursor_offset + 1), line
153153

154-
@edit_keys.on('<Ctrl-a>')
154+
@edit_keys.on(config='beginning_of_line_key')
155155
@edit_keys.on('<HOME>')
156156
def beginning_of_line(cursor_offset, line):
157157
return 0, line
158158

159-
@edit_keys.on('<Ctrl-e>')
159+
@edit_keys.on(config='end_of_line_key')
160160
@edit_keys.on('<END>')
161161
def end_of_line(cursor_offset, line):
162162
return len(line), line
@@ -188,9 +188,8 @@ def delete(cursor_offset, line):
188188
return (cursor_offset,
189189
line[:cursor_offset] + line[cursor_offset+1:])
190190

191-
@edit_keys.on('<Ctrl-h>')
192191
@edit_keys.on('<BACKSPACE>')
193-
@edit_keys.on(config='delete_key')
192+
@edit_keys.on(config='backspace_key')
194193
def backspace(cursor_offset, line):
195194
if cursor_offset == 0:
196195
return cursor_offset, line
@@ -201,7 +200,6 @@ def backspace(cursor_offset, line):
201200
return (cursor_offset - 1,
202201
line[:cursor_offset - 1] + line[cursor_offset:])
203202

204-
@edit_keys.on('<Ctrl-u>')
205203
@edit_keys.on(config='clear_line_key')
206204
def delete_from_cursor_back(cursor_offset, line):
207205
return 0, line[cursor_offset:]
@@ -215,7 +213,6 @@ def delete_rest_of_word(cursor_offset, line):
215213
return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:],
216214
line[cursor_offset:m.start()+cursor_offset+1])
217215

218-
@edit_keys.on('<Ctrl-w>')
219216
@edit_keys.on(config='clear_word_key')
220217
@kills_behind
221218
def delete_word_to_cursor(cursor_offset, line):
@@ -231,7 +228,7 @@ def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): #TODO not imple
231228
def yank_prev_killed_text(cursor_offset, line, cut_buffer):
232229
return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:]
233230

234-
@edit_keys.on('<Ctrl-t>')
231+
@edit_keys.on(config='transpose_chars_key')
235232
def transpose_character_before_cursor(cursor_offset, line):
236233
return (min(len(line), cursor_offset + 1),
237234
line[:cursor_offset-1] +
@@ -253,7 +250,7 @@ def delete_line(cursor_offset, line):
253250
def uppercase_next_word(cursor_offset, line):
254251
return cursor_offset, line #TODO Not implemented
255252

256-
@edit_keys.on('<Ctrl-k>')
253+
@edit_keys.on(config='kill_line_key')
257254
@kills_ahead
258255
def delete_from_cursor_forward(cursor_offset, line):
259256
return cursor_offset, line[:cursor_offset], line[cursor_offset:]

bpython/curtsiesfrontend/repl.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,8 @@ def process_key_event(self, e):
493493
self.incremental_search(reverse=True)
494494
elif e in ("<Esc+s>",):
495495
self.incremental_search()
496-
elif e in ("<BACKSPACE>", '<Ctrl-h>') and self.incremental_search_mode:
496+
elif (e in ("<BACKSPACE>",) + key_dispatch[self.config.backspace_key]
497+
and self.incremental_search_mode):
497498
self.add_to_incremental_search(self, backspace=True)
498499
elif e in self.edit_keys.cut_buffer_edits:
499500
self.readline_kill(e)

bpython/sample-config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ auto_display_list = True
4949

5050
[keyboard]
5151

52+
# All key bindings are shown commented out with their default binding
53+
5254
# pastebin = F8
5355
# last_output = F9
5456
# reimport = F6
@@ -61,6 +63,7 @@ auto_display_list = True
6163
# cut_to_buffer = C-k
6264
# search = C-o
6365
# yank_from_buffer = C-y
66+
# backspace = C-h
6467
# clear_word = C-w
6568
# clear_line = C-u
6669
# clear_screen = C-l

bpython/test/test_config.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import unittest
3+
import tempfile
34

45
from bpython import config
56

@@ -18,5 +19,13 @@ def test_load_theme(self):
1819
config.load_theme(struct, TEST_THEME_PATH, struct.color_scheme, defaults)
1920
self.assertEquals(struct.color_scheme, expected)
2021

21-
if __name__ == '__main__':
22-
unittest.main()
22+
def test_load_config(self):
23+
struct = config.Struct()
24+
with tempfile.NamedTemporaryFile() as f:
25+
f.write(''.encode('utf8'))
26+
f.write('[keyboard]\nhelp = C-h\n'.encode('utf8'))
27+
f.flush()
28+
config.loadini(struct, f.name)
29+
self.assertEqual(struct.help_key, 'C-h')
30+
self.assertEqual(struct.backspace_key, '')
31+

0 commit comments

Comments
 (0)