Skip to content

Commit fa08b74

Browse files
small paste events may contain <Ctrl-a>like events
1 parent 6df6a4a commit fa08b74

File tree

3 files changed

+65
-15
lines changed

3 files changed

+65
-15
lines changed

bpython/curtsiesfrontend/repl.py

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
Press {config.edit_config_key} to edit this config file.
8585
"""
8686
EXAMPLE_CONFIG_URL = 'https://raw.githubusercontent.com/bpython/bpython/master/bpython/sample-config'
87+
MAX_EVENTS_POSSIBLY_NOT_PASTE = 20 # more than this many events will be assumed to
88+
# be a true paste event, i.e. control characters
89+
# like '<Ctrl-a>' will be stripped
8790

8891
# This is needed for is_nop and should be removed once is_nop is fixed.
8992
if py3:
@@ -545,16 +548,25 @@ def process_control_event(self, e):
545548
ctrl_char = compress_paste_event(e)
546549
if ctrl_char is not None:
547550
return self.process_event(ctrl_char)
548-
simple_events = just_simple_events(e.events)
549-
source = preprocess(''.join(simple_events),
550-
self.interp.compile)
551-
552551
with self.in_paste_mode():
553-
for ee in source:
554-
if self.stdin.has_focus:
555-
self.stdin.process_event(ee)
556-
else:
557-
self.process_simple_keypress(ee)
552+
# Might not really be a paste, UI might just be lagging
553+
if (len(e.events) <= MAX_EVENTS_POSSIBLY_NOT_PASTE and
554+
any(not is_simple_event(ee) for ee in e.events)):
555+
for ee in e.events:
556+
if self.stdin.has_focus:
557+
self.stdin.process_event(ee)
558+
else:
559+
self.process_event(ee)
560+
else:
561+
simple_events = just_simple_events(e.events)
562+
source = preprocess(''.join(simple_events),
563+
self.interp.compile)
564+
for ee in source:
565+
if self.stdin.has_focus:
566+
self.stdin.process_event(ee)
567+
else:
568+
self.process_simple_keypress(ee)
569+
558570

559571
elif isinstance(e, bpythonevents.RunStartupFileEvent):
560572
try:
@@ -1611,11 +1623,24 @@ def just_simple_events(event_list):
16111623
pass # ignore events
16121624
elif e == '<SPACE>':
16131625
simple_events.append(' ')
1626+
elif len(e) > 1:
1627+
pass # get rid of <Ctrl-a> etc.
16141628
else:
16151629
simple_events.append(e)
16161630
return simple_events
16171631

16181632

1633+
def is_simple_event(e):
1634+
if isinstance(e, events.Event):
1635+
return False
1636+
if e in ("<Ctrl-j>", "<Ctrl-m>", "<PADENTER>", "\n", "\r", "<SPACE>"):
1637+
return True
1638+
if len(e) > 1:
1639+
return False
1640+
else:
1641+
return True
1642+
1643+
16191644
# TODO this needs some work to function again and be useful for embedding
16201645
def simple_repl():
16211646
refreshes = []

bpython/test/test_curtsies.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ def tick(self, dt=1):
4949

5050
class TestCurtsiesPasteDetection(TestCase):
5151
def test_paste_threshold(self):
52-
inputs = combined_events(EventGenerator(list('abc')))
53-
cb = combined_events(inputs, paste_threshold=3)
52+
eg = EventGenerator(list('abc'))
53+
cb = combined_events(eg, paste_threshold=3)
5454
e = next(cb)
5555
self.assertIsInstance(e, curtsies.events.PasteEvent)
5656
self.assertEqual(e.events, list('abc'))
5757
self.assertEqual(next(cb), None)
5858

59-
inputs = combined_events(EventGenerator(list('abc')))
60-
cb = combined_events(inputs, paste_threshold=4)
59+
eg = EventGenerator(list('abc'))
60+
cb = combined_events(eg, paste_threshold=4)
6161
self.assertEqual(next(cb), 'a')
6262
self.assertEqual(next(cb), 'b')
6363
self.assertEqual(next(cb), 'c')
@@ -67,8 +67,7 @@ def test_set_timeout(self):
6767
eg = EventGenerator('a', zip('bcdefg', [1, 2, 3, 3, 3, 4]))
6868
eg.schedule_event(curtsies.events.SigIntEvent(), 5)
6969
eg.schedule_event('h', 6)
70-
inputs = combined_events(eg)
71-
cb = combined_events(inputs, paste_threshold=3)
70+
cb = combined_events(eg, paste_threshold=3)
7271
self.assertEqual(next(cb), 'a')
7372
self.assertEqual(cb.send(0), None)
7473
self.assertEqual(next(cb), 'b')

bpython/test/test_curtsies_repl.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from bpython.test import (FixLanguageTestCase as TestCase, MagicIterMock, mock,
2121
builtin_target, unittest)
2222

23+
from curtsies import events
24+
2325

2426
def setup_config(conf):
2527
config_struct = config.Struct()
@@ -402,5 +404,29 @@ def test_startup_event_latin1(self):
402404
self.assertIn('a', self.repl.interp.locals)
403405

404406

407+
class TestCurtsiesPasteEvents(TestCase):
408+
409+
def setUp(self):
410+
self.repl = create_repl()
411+
412+
def test_control_events_in_small_paste(self):
413+
self.assertGreaterEqual(curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE, 6,
414+
'test assumes UI lag could cause 6 events')
415+
p = events.PasteEvent()
416+
p.events = ['a', 'b', 'c', 'd', '<Ctrl-a>', 'e']
417+
self.repl.process_event(p)
418+
self.assertEqual(self.repl.current_line, 'eabcd')
419+
420+
421+
def test_control_events_in_large_paste(self):
422+
"""Large paste events should ignore control characters"""
423+
p = events.PasteEvent()
424+
p.events = (['a', '<Ctrl-a>'] +
425+
['e'] * curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE)
426+
self.repl.process_event(p)
427+
self.assertEqual(self.repl.current_line,
428+
'a' + 'e'*curtsiesrepl.MAX_EVENTS_POSSIBLY_NOT_PASTE)
429+
430+
405431
if __name__ == '__main__':
406432
unittest.main()

0 commit comments

Comments
 (0)