3737
3838import sys
3939import os
40+ import time
4041import locale
4142import signal
4243from types import ModuleType
@@ -438,9 +439,15 @@ def notify(self, s, n=10):
438439
439440class URWIDRepl (repl .Repl ):
440441
442+ _time_between_redraws = .05 # seconds
443+
441444 def __init__ (self , event_loop , palette , interpreter , config ):
442445 repl .Repl .__init__ (self , interpreter , config )
443446
447+ self ._redraw_handle = None
448+ self ._redraw_pending = False
449+ self ._redraw_time = 0
450+
444451 self .listbox = BPythonListBox (urwid .SimpleListWalker ([]))
445452
446453 # String is straight from bpython.cli
@@ -485,6 +492,13 @@ def echo(self, orig_s):
485492 self .current_output = urwid .Text (('output' , s ))
486493 if self .edit is None :
487494 self .listbox .body .append (self .current_output )
495+ # Focus the widget we just added to force the
496+ # listbox to scroll. This causes output to scroll
497+ # if the user runs a blocking call that prints
498+ # more than a screenful, instead of staying
499+ # scrolled to the previous input line and then
500+ # jumping to the bottom when done.
501+ self .listbox .set_focus (len (self .listbox .body ) - 1 )
488502 else :
489503 self .listbox .body .insert (- 1 , self .current_output )
490504 # The edit widget should be focused and *stay* focused.
@@ -496,9 +510,37 @@ def echo(self, orig_s):
496510 ('output' , self .current_output .text + s ))
497511 if orig_s .endswith ('\n ' ):
498512 self .current_output = None
499- # TODO: maybe do the redraw after a short delay
500- # (for performance)
501- self .main_loop .draw_screen ()
513+
514+ # If we hit this repeatedly in a loop the redraw is rather
515+ # slow (testcase: pprint(__builtins__). So if we have recently
516+ # drawn the screen already schedule a call in the future.
517+ #
518+ # Unfortunately we may hit this function repeatedly through a
519+ # blocking call triggered by the user, in which case our
520+ # timeout will not run timely as we do not return to urwid's
521+ # eventloop. So we manually check if our timeout has long
522+ # since expired, and redraw synchronously if it has.
523+ if self ._redraw_handle is None :
524+ self .main_loop .draw_screen ()
525+
526+ def maybe_redraw (loop , self ):
527+ if self ._redraw_pending :
528+ loop .draw_screen ()
529+ self ._redraw_pending = False
530+
531+ self ._redraw_handle = None
532+
533+ self ._redraw_handle = self .main_loop .set_alarm_in (
534+ self ._time_between_redraws , maybe_redraw , self )
535+ self ._redraw_time = time .time ()
536+ else :
537+ self ._redraw_pending = True
538+ now = time .time ()
539+ if now - self ._redraw_time > 2 * self ._time_between_redraws :
540+ # The timeout is well past expired, assume we're
541+ # blocked and redraw synchronously.
542+ self .main_loop .draw_screen ()
543+ self ._redraw_time = now
502544
503545 def current_line (self ):
504546 """Return the current line (the one the cursor is in)."""
0 commit comments