Skip to content

Commit ad51de2

Browse files
author
Marien Zwart
committed
urwid frontend: handle long-running commands and lengthy output better.
Redrawing the screen is relatively slow, so rate-limiting it greatly speeds up inputs that result in a lot of output quickly. Also, scrolling to the bottom while waiting for a command to run out of output makes it more obvious we did not get stuck. Both issues reported by tos9 over irc.
1 parent 4188293 commit ad51de2

File tree

1 file changed

+45
-3
lines changed

1 file changed

+45
-3
lines changed

bpython/urwid.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import sys
3939
import os
40+
import time
4041
import locale
4142
import signal
4243
from types import ModuleType
@@ -438,9 +439,15 @@ def notify(self, s, n=10):
438439

439440
class 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

Comments
 (0)