Skip to content

Commit d16c649

Browse files
committed
Merged upstream changes
2 parents 66a117c + 32f0ab1 commit d16c649

File tree

8 files changed

+94
-66
lines changed

8 files changed

+94
-66
lines changed

bpython/curtsies.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,20 @@ def process_event(e):
6868
repl.process_event(e)
6969
except (SystemExitFromCodeGreenlet, SystemExit) as err:
7070
array, cursor_pos = repl.paint(about_to_exit=True, user_quit=isinstance(err, SystemExitFromCodeGreenlet))
71-
term.render_to_terminal(array, cursor_pos)
71+
scrolled = term.render_to_terminal(array, cursor_pos)
72+
repl.scroll_offset += scrolled
7273
raise
7374
else:
7475
array, cursor_pos = repl.paint()
75-
term.render_to_terminal(array, cursor_pos)
76+
scrolled = term.render_to_terminal(array, cursor_pos)
77+
repl.scroll_offset += scrolled
7678

7779
if paste:
78-
repl.process_event(tc.get_event()) #first event will always be a window size set
80+
repl.process_event(term.get_annotated_event()) #first event will always be a window size set
7981
process_event(paste)
8082

8183
while True:
82-
process_event(tc.get_event(idle=find_iterator))
84+
process_event(term.get_annotated_event(idle=find_iterator))
8385

8486
if __name__ == '__main__':
8587
sys.exit(main())

bpython/curtsiesfrontend/coderunner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def sigint_handler(self, *args):
146146
raise KeyboardInterrupt()
147147
else:
148148
logging.debug('sigint while fufilling code request sigint handler running!')
149-
self.sigint_happened = True
149+
self.sigint_happened_in_main_greenlet = True
150150

151151
def _blocking_run_code(self):
152152
try:

bpython/curtsiesfrontend/friendly.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

bpython/curtsiesfrontend/manual_readline.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
and the cursor location
55
in the order of description at http://www.bigsmoke.us/readline/shortcuts"""
66

7-
from bpython.curtsiesfrontend.friendly import NotImplementedError
87
import re
98
char_sequences = {}
109

@@ -104,7 +103,7 @@ def delete_word_to_cursor(cursor_offset, line):
104103

105104
@on('\x1by')
106105
def yank_prev_prev_killed_text(cursor_offset, line):
107-
raise NotImplementedError()
106+
return cursor_offset, line #TODO Not implemented
108107

109108
@on('\x14')
110109
def transpose_character_before_cursor(cursor_offset, line):
@@ -116,7 +115,7 @@ def transpose_character_before_cursor(cursor_offset, line):
116115

117116
@on('\x1bt')
118117
def transpose_word_before_cursor(cursor_offset, line):
119-
raise NotImplementedError()
118+
return cursor_offset, line #TODO Not implemented
120119

121120
# bonus functions (not part of readline)
122121

@@ -126,11 +125,11 @@ def delete_line(cursor_offset, line):
126125

127126
@on('\x1bu')
128127
def uppercase_next_word(cursor_offset, line):
129-
raise NotImplementedError()
128+
return cursor_offset, line #TODO Not implemented
130129

131130
@on('\x1bc')
132131
def titlecase_next_word(cursor_offset, line):
133-
raise NotImplementedError()
132+
return cursor_offset, line #TODO Not implemented
134133

135134
@on('\x1b\x7f')
136135
@on('\xff')

bpython/curtsiesfrontend/repl.py

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from bpython.curtsiesfrontend import sitefix; sitefix.monkeypatch_quit()
3131
import bpython.curtsiesfrontend.replpainter as paint
3232
import curtsies.events as events
33-
from bpython.curtsiesfrontend.friendly import NotImplementedError
3433
from bpython.curtsiesfrontend.coderunner import CodeRunner, FakeOutput
3534

3635
#TODO figure out how config.list_win_visible behaves and implement it, or stop using it
@@ -263,7 +262,9 @@ def process_event(self, e):
263262
self.run_code_and_maybe_finish()
264263
elif isinstance(e, events.WindowChangeEvent):
265264
logging.debug('window change to %d %d', e.width, e.height)
265+
self.scroll_offset -= e.cursor_dy
266266
self.width, self.height = e.width, e.height
267+
267268
elif self.status_bar.has_focus:
268269
return self.status_bar.process_event(e)
269270
elif self.stdin.has_focus:
@@ -275,9 +276,15 @@ def process_event(self, e):
275276
self.update_completion()
276277
return
277278
elif isinstance(e, events.PasteEvent):
279+
ctrl_char = compress_paste_event(e)
280+
if ctrl_char is not None:
281+
return self.process_event(ctrl_char)
278282
with self.in_paste_mode():
279283
for ee in e.events:
280-
self.process_simple_event(ee)
284+
if self.stdin.has_focus:
285+
self.stdin.process_event(ee)
286+
else:
287+
self.process_simple_event(ee)
281288
self.update_completion()
282289

283290
elif e in self.rl_char_sequences:
@@ -295,23 +302,23 @@ def process_event(self, e):
295302
self._current_line = self.rl_history.forward(False)
296303
self.cursor_offset_in_line = len(self._current_line)
297304
self.update_completion()
298-
elif e in key_dispatch[self.config.search_key]: #TODO
299-
raise NotImplementedError()
305+
elif e in key_dispatch[self.config.search_key]: #TODO Not Implemented
306+
pass
300307
#TODO add rest of history commands
301308

302309
# Need to figure out what these are, but I think they belong in manual_realine
303310
# under slightly different names
304-
elif e in key_dispatch[self.config.cut_to_buffer_key]: #TODO
305-
raise NotImplementedError()
306-
elif e in key_dispatch[self.config.yank_from_buffer_key]: #TODO
307-
raise NotImplementedError()
311+
elif e in key_dispatch[self.config.cut_to_buffer_key]: #TODO Not Implemented
312+
pass
313+
elif e in key_dispatch[self.config.yank_from_buffer_key]: #TODO Not Implemented
314+
pass
308315

309316
elif e in key_dispatch[self.config.clear_screen_key]:
310317
self.request_paint_to_clear_screen = True
311-
elif e in key_dispatch[self.config.last_output_key]: #TODO
312-
raise NotImplementedError()
313-
elif e in key_dispatch[self.config.show_source_key]: #TODO
314-
raise NotImplementedError()
318+
elif e in key_dispatch[self.config.last_output_key]: #TODO Not Implemented
319+
pass
320+
elif e in key_dispatch[self.config.show_source_key]: #TODO Not Implemented
321+
pass
315322
elif e in key_dispatch[self.config.suspend_key]:
316323
raise SystemExit()
317324
elif e in ("",) + key_dispatch[self.config.exit_key]:
@@ -414,7 +421,8 @@ def process_simple_event(self, e):
414421
elif isinstance(e, events.Event):
415422
pass # ignore events
416423
else:
417-
self.add_normal_character(e if len(e) == 1 else e[-1]) #strip control seq
424+
if len(e) == 1:
425+
self.add_normal_character(e if len(e) == 1 else e[-1]) #strip control seq
418426

419427
def send_current_block_to_external_editor(self, filename=None):
420428
text = self.send_to_external_editor(self.get_current_block())
@@ -551,7 +559,7 @@ def unhighlight_paren(self):
551559
logging.debug('trying to unhighlight a paren on line %r', lineno)
552560
logging.debug('with these tokens: %r', saved_tokens)
553561
new = bpythonparse(format(saved_tokens, self.formatter))
554-
self.display_buffer[lineno] = self.display_buffer[lineno].setslice(0, len(new), new)
562+
self.display_buffer[lineno] = self.display_buffer[lineno].setslice_with_length(0, len(new), new, len(self.display_buffer[lineno]))
555563

556564
def clear_current_block(self, remove_from_history=True):
557565
self.display_buffer = []
@@ -626,26 +634,30 @@ def current_word(self):
626634
so must match its definition of current word - changing how it behaves
627635
has many repercussions.
628636
"""
629-
words = re.split(r'([\w_][\w0-9._]*[(]?)', self._current_line)
630-
chars = 0
631-
cw = None
632-
for word in words:
633-
chars += len(word)
634-
if chars == self.cursor_offset_in_line and word and word.count(' ') == 0:
635-
cw = word
636-
if cw and re.match(r'^[\w_][\w0-9._]*[(]?$', cw):
637-
return cw
637+
638+
start, end, word = self._get_current_word()
639+
return word
640+
641+
def _get_current_word(self):
642+
pos = self.cursor_offset_in_line
643+
644+
matches = list(re.finditer(r'[\w_][\w0-9._]*[(]?', self._current_line))
645+
start = pos
646+
end = pos
647+
word = None
648+
for m in matches:
649+
if m.start() < pos and m.end() >= pos:
650+
start = m.start()
651+
end = m.end()
652+
word = m.group()
653+
return (start, end, word)
638654

639655
@current_word.setter
640656
def current_word(self, value):
641-
# current word means word cursor is at the end of, so delete from cursor back to [ ."']
642-
pos = self.cursor_offset_in_line - 1
643-
if pos > -1 and self._current_line[pos] not in tuple(' :)'):
644-
pos -= 1
645-
while pos > -1 and self._current_line[pos] not in tuple(' :()\'"'):
646-
pos -= 1
647-
start = pos + 1; del pos
648-
self._current_line = self._current_line[:start] + value + self._current_line[self.cursor_offset_in_line:]
657+
# current word means word cursor is at the end of
658+
start, end, word = self._get_current_word()
659+
660+
self._current_line = self._current_line[:start] + value + self._current_line[end:]
649661
self.cursor_offset_in_line = start + len(value)
650662

651663
@property
@@ -688,17 +700,6 @@ def current_output_line(self, value):
688700
def paint(self, about_to_exit=False, user_quit=False):
689701
"""Returns an array of min_height or more rows and width columns, plus cursor position
690702
691-
Also increments self.scroll_offset by the amount the terminal must have scrolled
692-
to display the entire array.
693-
"""
694-
arr, (row, col) = self._paint(about_to_exit=about_to_exit, user_quit=user_quit)
695-
if arr.height > self.height:
696-
self.scroll_offset += arr.height - self.height
697-
return arr, (row, col)
698-
699-
def _paint(self, about_to_exit=False, user_quit=False):
700-
"""Returns an array of min_height or more rows and width columns, plus cursor position
701-
702703
Paints the entire screen - ideally the terminal display layer will take a diff and only
703704
write to the screen in portions that have changed, but the idea is that we don't need
704705
to worry about that here, instead every frame is completely redrawn because
@@ -726,7 +727,6 @@ def _paint(self, about_to_exit=False, user_quit=False):
726727
#TODO test case of current line filling up the whole screen (there aren't enough rows to show it)
727728

728729
if current_line_start_row < 0: #if current line trying to be drawn off the top of the screen
729-
#assert True, 'no room for current line: contiguity of history broken!'
730730
logging.debug('#<---History contiguity broken by rewind--->')
731731
msg = "#<---History contiguity broken by rewind--->"
732732
arr[0, 0:min(len(msg), width)] = [msg[:width]]
@@ -758,7 +758,8 @@ def _paint(self, about_to_exit=False, user_quit=False):
758758

759759
lines = paint.display_linize(self.current_cursor_line+'X', width)
760760
# extra character for space for the cursor
761-
cursor_row = current_line_start_row + len(lines) - 1
761+
current_line_end_row = current_line_start_row + len(lines) - 1
762+
cursor_row = current_line_start_row + (len(self.current_cursor_line) - len(self._current_line) + self.cursor_offset_in_line) / width
762763
if self.stdin.has_focus:
763764
cursor_column = (len(self.current_stdouterr_line) + self.stdin.cursor_offset_in_line) % width
764765
assert cursor_column >= 0, cursor_column
@@ -772,15 +773,15 @@ def _paint(self, about_to_exit=False, user_quit=False):
772773
if self.list_win_visible:
773774
logging.debug('infobox display code running')
774775
visible_space_above = history.height
775-
visible_space_below = min_height - cursor_row - 1
776+
visible_space_below = min_height - current_line_end_row - 1
776777

777778
info_max_rows = max(visible_space_above, visible_space_below)
778779
infobox = paint.paint_infobox(info_max_rows, int(width * self.config.cli_suggestion_width), self.matches, self.argspec, self.current_word, self.docstring, self.config)
779780

780781
if visible_space_above >= infobox.height and self.config.curtsies_list_above:
781782
arr[current_line_start_row - infobox.height:current_line_start_row, 0:infobox.width] = infobox
782783
else:
783-
arr[cursor_row + 1:cursor_row + 1 + infobox.height, 0:infobox.width] = infobox
784+
arr[current_line_end_row + 1:current_line_end_row + 1 + infobox.height, 0:infobox.width] = infobox
784785
logging.debug('slamming infobox of shape %r into arr of shape %r', infobox.shape, arr.shape)
785786

786787
logging.debug('about to exit: %r', about_to_exit)
@@ -929,6 +930,26 @@ def getstdout(self):
929930
s = '\n'.join([x.s if isinstance(x, FmtStr) else x for x in lines]
930931
) if lines else ''
931932
return s
933+
934+
def compress_paste_event(paste_event):
935+
"""If all events in a paste event are identical and not simple characters, returns one of them
936+
937+
Useful for when the UI is running so slowly that repeated keypresses end up in a paste event.
938+
If we value not getting delayed and assume the user is holding down a key to produce such frequent
939+
key events, it makes sense to drop some of the events.
940+
"""
941+
if not all(paste_event.events[0] == e for e in paste_event.events):
942+
return None
943+
event = paste_event.events[0]
944+
if len(event) > 1:# basically "is there a special curses names for this key?"
945+
return event
946+
elif ord(event) < 0x20:
947+
return event
948+
elif event == '\x7f':
949+
return event
950+
else:
951+
return None
952+
932953
def simple_repl():
933954
refreshes = []
934955
def request_refresh():

bpython/curtsiesfrontend/replpainter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def formatted_argspec(argspec, columns, config):
9595
for i, arg in enumerate(args):
9696
kw = None
9797
if kwargs and i >= len(args) - len(kwargs):
98-
kw = repr(kwargs[i - (len(args) - len(kwargs))])
98+
kw = str(kwargs[i - (len(args) - len(kwargs))])
9999
color = token_color if in_arg in (i, arg) else arg_color
100100
if i == in_arg or arg == in_arg:
101101
color = bolds[color]

bpython/repl.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,6 @@ def __init__(self, interp, config):
388388
self._C = {}
389389
self.prev_block_finished = 0
390390
self.interact = Interaction(self.config)
391-
self.ps1 = '>>> '
392-
self.ps2 = '... '
393391
# previous pastebin content to prevent duplicate pastes, filled on call
394392
# to repl.pastebin
395393
self.prev_pastebin_content = ''
@@ -403,6 +401,20 @@ def __init__(self, interp, config):
403401
self.rl_history.load(pythonhist,
404402
getpreferredencoding() or "ascii")
405403

404+
@property
405+
def ps1(self):
406+
try:
407+
return str(sys.ps1)
408+
except AttributeError:
409+
return '>>> '
410+
411+
@property
412+
def ps2(self):
413+
try:
414+
return str(sys.ps2)
415+
except AttributeError:
416+
return '... '
417+
406418
def startup(self):
407419
"""
408420
Execute PYTHONSTARTUP file if it exits. Call this after front

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def initialize_options(self):
153153
'pygments'
154154
],
155155
extras_require = {
156-
'curtsies': ['curtsies>=0.0.28', 'greenlet'],
156+
'curtsies': ['curtsies>=0.0.32', 'greenlet'],
157157
'urwid' : ['urwid']
158158
},
159159
tests_require = ['mock'],

0 commit comments

Comments
 (0)