Skip to content

Commit c92b043

Browse files
yank working!
1 parent 60a0687 commit c92b043

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed

bpython/curtsiesfrontend/manual_readline.py

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class AbstractEdits(object):
1717
'line': 'hello world',
1818
'cursor_offset': 5,
1919
'cut_buffer': 'there',
20-
'indent': 4,
2120
}
2221

2322
def __contains__(self, key):
@@ -28,15 +27,22 @@ def __contains__(self, key):
2827
else:
2928
return True
3029

31-
def add(self, key, func):
30+
def add(self, key, func, overwrite=False):
3231
if key in self:
33-
raise ValueError('key %r already has a mapping' % (key,))
32+
if overwrite:
33+
del self[key]
34+
else:
35+
raise ValueError('key %r already has a mapping' % (key,))
3436
params = inspect.getargspec(func)[0]
3537
args = dict((k, v) for k, v in self.default_kwargs.items() if k in params)
3638
r = func(**args)
3739
if len(r) == 2:
40+
if hasattr(func, 'kills'):
41+
raise ValueError('function %r returns two values, but has a kills attribute' % (func,))
3842
self.simple_edits[key] = func
3943
elif len(r) == 3:
44+
if not hasattr(func, 'kills'):
45+
raise ValueError('function %r returns three values, but has no kills attribute' % (func,))
4046
self.cut_buffer_edits[key] = func
4147
else:
4248
raise ValueError('return type of function %r not recognized' % (func,))
@@ -52,11 +58,20 @@ def call(self, key, **kwargs):
5258
args = dict((k, v) for k, v in kwargs.items() if k in params)
5359
return func(**args)
5460

61+
def call_without_cut(self, key, **kwargs):
62+
"""Looks up the function and calls it, returning only line and cursor offset"""
63+
r = self.call_for_two(key, **kwargs)
64+
return r[:2]
65+
5566
def __getitem__(self, key):
5667
if key in self.simple_edits: return self.simple_edits[key]
5768
if key in self.cut_buffer_edits: return self.cut_buffer_edits[key]
5869
raise KeyError("key %r not mapped" % (key,))
5970

71+
def __delitem__(self, key):
72+
if key in self.simple_edits: del self.simple_edits[key]
73+
elif key in self.cut_buffer_edits: del self.cut_buffer_edits[key]
74+
else: raise KeyError("key %r not mapped" % (key,))
6075

6176
class UnconfiguredEdits(AbstractEdits):
6277
"""Maps key to edit functions, and bins them by what parameters they take.
@@ -104,7 +119,8 @@ def __init__(self, simple_edits, cut_buffer_edits, awaiting_config, config, key_
104119
self.simple_edits = dict(simple_edits)
105120
self.cut_buffer_edits = dict(cut_buffer_edits)
106121
for attr, func in awaiting_config.items():
107-
super(ConfiguredEdits, self).add(key_dispatch[getattr(config, attr)], func)
122+
for key in key_dispatch[getattr(config, attr)]:
123+
super(ConfiguredEdits, self).add(key, func, overwrite=True)
108124

109125
def add_config_attr(self, config_attr, func):
110126
raise NotImplementedError("Config already set on this mapping")
@@ -117,6 +133,14 @@ def add(self, key, func):
117133
# Because the edits.on decorator runs the functions, functions which depend
118134
# on other functions must be declared after their dependencies
119135

136+
def kills_behind(func):
137+
func.kills = 'behind'
138+
return func
139+
140+
def kills_ahead(func):
141+
func.kills = 'ahead'
142+
return func
143+
120144
@edit_keys.on('<Ctrl-b>')
121145
@edit_keys.on('<LEFT>')
122146
def left_arrow(cursor_offset, line):
@@ -183,22 +207,29 @@ def delete_from_cursor_back(cursor_offset, line):
183207
return 0, line[cursor_offset:]
184208

185209
@edit_keys.on('<Esc+d>') # option-d
210+
@kills_ahead
186211
def delete_rest_of_word(cursor_offset, line):
187212
m = re.search(r'\w\b', line[cursor_offset:])
188213
if not m:
189-
return cursor_offset, line
190-
return cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:]
214+
return cursor_offset, line, ''
215+
return (cursor_offset, line[:cursor_offset] + line[m.start()+cursor_offset+1:],
216+
line[cursor_offset:m.start()+cursor_offset+1])
191217

192218
@edit_keys.on('<Ctrl-w>')
193219
@edit_keys.on(config='clear_word_key')
220+
@kills_behind
194221
def delete_word_to_cursor(cursor_offset, line):
195222
matches = list(re.finditer(r'\s\S', line[:cursor_offset]))
196223
start = matches[-1].start()+1 if matches else 0
197-
return start, line[:start] + line[cursor_offset:]
224+
return start, line[:start] + line[cursor_offset:], line[start:cursor_offset]
198225

199226
@edit_keys.on('<Esc+y>')
200-
def yank_prev_prev_killed_text(cursor_offset, line):
201-
return cursor_offset, line #TODO Not implemented
227+
def yank_prev_prev_killed_text(cursor_offset, line, cut_buffer): #TODO not implemented - just prev
228+
return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:]
229+
230+
@edit_keys.on(config='yank_from_buffer_key')
231+
def yank_prev_killed_text(cursor_offset, line, cut_buffer):
232+
return cursor_offset+len(cut_buffer), line[:cursor_offset] + cut_buffer + line[cursor_offset:]
202233

203234
@edit_keys.on('<Ctrl-t>')
204235
def transpose_character_before_cursor(cursor_offset, line):
@@ -223,21 +254,24 @@ def uppercase_next_word(cursor_offset, line):
223254
return cursor_offset, line #TODO Not implemented
224255

225256
@edit_keys.on('<Ctrl-k>')
257+
@kills_ahead
226258
def delete_from_cursor_forward(cursor_offset, line):
227-
return cursor_offset, line[:cursor_offset]
259+
return cursor_offset, line[:cursor_offset], line[cursor_offset:]
228260

229261
@edit_keys.on('<Esc+c>')
230262
def titlecase_next_word(cursor_offset, line):
231263
return cursor_offset, line #TODO Not implemented
232264

233265
@edit_keys.on('<Esc+BACKSPACE>')
234266
@edit_keys.on('<Meta-BACKSPACE>')
267+
@kills_behind
235268
def delete_word_from_cursor_back(cursor_offset, line):
236269
"""Whatever my option-delete does in bash on my mac"""
237270
if not line:
238271
return cursor_offset, line
239272
starts = [m.start() for m in list(re.finditer(r'\b\w', line)) if m.start() < cursor_offset]
240273
if starts:
241-
return starts[-1], line[:starts[-1]] + line[cursor_offset:]
242-
return cursor_offset, line
274+
return starts[-1], line[:starts[-1]] + line[cursor_offset:], line[starts[-1]:cursor_offset]
275+
return cursor_offset, line, ''
276+
243277

bpython/curtsiesfrontend/repl.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -458,12 +458,17 @@ def process_key_event(self, e):
458458
self.up_one_line()
459459
elif e in ("<DOWN>",) + key_dispatch[self.config.down_one_line_key]:
460460
self.down_one_line()
461-
elif e in self.edit_keys:
462-
self.cursor_offset, self.current_line = self.edit_keys[e](self.cursor_offset, self.current_line)
461+
elif e in ("<Ctrl-d>",):
462+
self.on_control_d()
463+
elif e in self.edit_keys.cut_buffer_edits:
464+
self.readline_kill(e)
465+
elif e in self.edit_keys.simple_edits:
466+
self.cursor_offset, self.current_line = self.edit_keys.call(e,
467+
cursor_offset=self.cursor_offset,
468+
line=self.current_line,
469+
cut_buffer=self.cut_buffer)
463470
elif e in key_dispatch[self.config.cut_to_buffer_key]:
464471
self.cut_to_buffer()
465-
elif e in key_dispatch[self.config.yank_from_buffer_key]:
466-
self.yank_from_buffer()
467472
elif e in key_dispatch[self.config.reimport_key]:
468473
self.clear_modules_and_reevaluate()
469474
elif e in key_dispatch[self.config.toggle_file_watch_key]:
@@ -476,8 +481,6 @@ def process_key_event(self, e):
476481
self.pager(self.help_text())
477482
elif e in key_dispatch[self.config.suspend_key]:
478483
raise SystemExit()
479-
elif e in ("<Ctrl-d>",):
480-
self.on_control_d()
481484
elif e in key_dispatch[self.config.exit_key]:
482485
raise SystemExit()
483486
elif e in ("\n", "\r", "<PADENTER>", "<Ctrl-j>", "<Ctrl-m>"):
@@ -504,6 +507,16 @@ def process_key_event(self, e):
504507
else:
505508
self.add_normal_character(e)
506509

510+
def readline_kill(self, e):
511+
func = self.edit_keys[e]
512+
self.cursor_offset, self.current_line, cut = func(self.cursor_offset, self.current_line)
513+
if self.last_events[-2] == e: # consecutive kill commands are cumulative
514+
if func.kills == 'ahead': self.cut_buffer += cut
515+
elif func.kills == 'behind': self.cut_buffer = cut + self.cut_buffer
516+
else: raise ValueError("cut had value other than 'ahead' or 'behind'")
517+
else:
518+
self.cut_buffer = cut
519+
507520
def on_enter(self, insert_into_history=True):
508521
self.cursor_offset = -1 # so the cursor isn't touching a paren
509522
self.unhighlight_paren() # in unhighlight_paren

bpython/test/test_manual_readline.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ class config: att = 'c'
243243
self.assertEqual(configured_edits.call('c', cursor_offset=5, line='asfd'),
244244
('hi', 2))
245245

246+
def test_actual(self):
247+
class config: att = 'c'
248+
key_dispatch = {'c': 'c'}
249+
configured_edits = self.edits.mapping_with_config(config, key_dispatch)
250+
246251

247252
if __name__ == '__main__':
248253
unittest.main()

0 commit comments

Comments
 (0)