Skip to content

Commit

Permalink
Handle resizing outside of signal handler
Browse files Browse the repository at this point in the history
  • Loading branch information
avylove committed Aug 23, 2020
1 parent b278ba2 commit 021a256
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 45 deletions.
3 changes: 1 addition & 2 deletions enlighten/_basecounter.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,7 @@ def refresh(self, flush=True, elapsed=None):

if self.enabled:
self.last_update = time.time()
self.manager.write(output=self.format(elapsed=elapsed),
flush=flush, counter=self)
self.manager.write(output=self.format, flush=flush, counter=self, elapsed=elapsed)

def _fill_text(self, text, width, offset=None):
"""
Expand Down
54 changes: 38 additions & 16 deletions enlighten/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(self, stream=None, counter_class=Counter, **kwargs):
self.height = self.term.height
self.process_exit = False
self.refresh_lock = False
self._resize = False
self.resize_lock = False
self.scroll_offset = 1
self.width = self.term.width
Expand Down Expand Up @@ -255,9 +256,21 @@ def _add_counter(self, counter_class, *args, **kwargs):

return new

def _resize_handler(self, *args, **kwarg): # pylint: disable=unused-argument
def _stage_resize(self, *args, **kwarg): # pylint: disable=unused-argument
"""
Called when a window resize signal is detected
"""

# Set semaphore to trigger resize on next write
self._resize = True

# Reset update time to avoid any delay in resize
for counter in self.counters:
counter.last_update = 0

def _resize_handler(self):
"""
Called when a window resize has been detected
Resets the scroll window
"""
Expand All @@ -273,15 +286,6 @@ def _resize_handler(self, *args, **kwarg): # pylint: disable=unused-argument
term.clear_cache()
newHeight = term.height
newWidth = term.width
lastHeight = lastWidth = 0

while newHeight != lastHeight or newWidth != lastWidth:
lastHeight = newHeight
lastWidth = newWidth
time.sleep(.2)
term.clear_cache()
newHeight = term.height
newWidth = term.width

if newWidth < self.width:
offset = (self.scroll_offset - 1) * (1 + self.width // newWidth)
Expand Down Expand Up @@ -321,7 +325,7 @@ def _set_scroll_area(self, force=False):
if not self.process_exit:
atexit.register(self._at_exit)
if not self.no_resize and RESIZE_SUPPORTED:
signal.signal(signal.SIGWINCH, self._resize_handler)
signal.signal(signal.SIGWINCH, self._stage_resize)
self.process_exit = True

if self.set_scroll:
Expand Down Expand Up @@ -450,27 +454,45 @@ def stop(self):

self._flush_streams()

def write(self, output='', flush=True, counter=None):
def write(self, output='', flush=True, counter=None, **kwargs):
"""
Args:
output(str): Output string
output(str): Output string or callable
flush(bool): Flush the output stream after writing
counter(:py:class:`Counter`): Bar being written (for position and auto-refresh)
kwargs(dict): Additional arguments passed when output is callable
Write to the stream.
Write to stream at a given position
The position is determined by the counter or defaults to the bottom of the termina;
If ``output`` is callable, it will be called with any additional keyword arguments
to produce the output string
"""

if not self.enabled:
return

# If resize signal was caught, handle resize
if self._resize and not self.resize_lock:
try:
self._resize_handler()
finally:
self._resize = False

return

position = self.counters[counter] if counter else 0
stream = self.stream
term = self.term

# If output is callable, call it with supplied arguments
if callable(output):
output = output(**kwargs)

try:
term.move_to(0, term.height - position)
# Include \r and term call to cover most conditions
stream.write(u'\r' + term.clear_eol + output)
self.stream.write(u'\r' + term.clear_eol + output)

finally:
# Reset position and scrolling
Expand Down
6 changes: 5 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,11 @@ def __init__(self, counter_class=Counter, **kwargs):
self.output = []
self.remove_calls = 0

def write(self, output='', flush=True, counter=None):
def write(self, output='', flush=True, counter=None, **kwargs):

if callable(output):
output = output(**kwargs)

self.output.append('write(output=%s, flush=%s, position=%s)' %
(output, flush, counter.position))

Expand Down
63 changes: 39 additions & 24 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,15 +343,15 @@ def test_set_scroll_area(self):
stdread = self.tty.stdread
self.assertEqual(manager.scroll_offset, 1)
self.assertFalse(manager.process_exit)
self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)

with mock.patch('enlighten._manager.atexit') as atexit:
with mock.patch.object(term, 'change_scroll'):
manager._set_scroll_area()
self.assertEqual(term.change_scroll.call_count, 1) # pylint: disable=no-member

self.assertEqual(manager.scroll_offset, 4)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)

self.assertEqual(stdread.readline(), term.move(24, 0) + '\n')
self.assertEqual(stdread.readline(), '\n')
Expand Down Expand Up @@ -458,7 +458,7 @@ def test_stop(self):
manager._set_scroll_area()

self.assertEqual(manager.scroll_offset, 5)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
self.assertTrue(manager.process_exit)

# Clear stream
Expand All @@ -473,7 +473,7 @@ def test_stop(self):
# No output, No changes
self.tty.stdout.write(u'X\n')
self.assertEqual(self.tty.stdread.readline(), 'X\n')
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
self.assertTrue(manager.process_exit)

manager.enabled = True
Expand Down Expand Up @@ -507,7 +507,7 @@ def test_stop_no_set_scroll(self):
manager._set_scroll_area()

self.assertEqual(manager.scroll_offset, 5)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
self.assertTrue(manager.process_exit)

# Stream empty
Expand Down Expand Up @@ -584,15 +584,19 @@ def test_stop_position_1(self):
self.assertTrue(termfeed.called)

def test_resize_handler(self):
"""
Resize lock must be False for handler to run
Terminal size is cached unless resize handler runs
"""

with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
mockheight.side_effect = [80, 85, 87, 70, 70]
manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
counter3 = MockCounter(manager=manager)
manager.counters[counter3] = 3
manager.scroll_offset = 4
term = manager.term

manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
counter3 = MockCounter(manager=manager)
manager.counters[counter3] = 3
manager.scroll_offset = 4
term = manager.term
with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
mockheight.return_value = 70

manager.resize_lock = True
with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
Expand All @@ -608,7 +612,6 @@ def test_resize_handler(self):
self.assertEqual(counter3.calls, [])

manager.resize_lock = False
mockheight.side_effect = [80, 85, 87, 70, 70]
with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
manager._resize_handler()
self.assertEqual(ssa.call_count, 1)
Expand All @@ -621,25 +624,37 @@ def test_resize_handler(self):

self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)'])

def test_resize_handler_no_change(self):
def test_resize(self):
"""
Test a resize event
"""
manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
counter3 = MockCounter(manager=manager)
counter3.last_update = time.time()
manager.counters[counter3] = 3
manager.scroll_offset = 4
term = manager.term

# simulate resize
manager._stage_resize()
self.assertTrue(manager._resize)
self.assertEqual(counter3.last_update, 0)

with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
mockheight.side_effect = [80, 85, 87, 80, 80]
mockheight.return_value = 70

manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
counter3 = MockCounter(manager=manager)
manager.counters[counter3] = 3
manager.scroll_offset = 4
# resize doesn't happen until a write is called
self.assertEqual(manager.width, 80)

with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
manager._resize_handler()
manager.write()
self.assertEqual(ssa.call_count, 1)

self.assertEqual(manager.width, 80)

self.assertEqual(manager.width, 70)
self.tty.stdout.write(u'X\n')
self.assertEqual(self.tty.stdread.readline(), 'X\n')

self.assertEqual(self.tty.stdread.readline(), term.move(19, 0) + term.clear_eos + 'X\n')
self.assertFalse(manager.resize_lock)
self.assertFalse(manager._resize)
self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)'])

def test_resize_handler_height_only(self):
Expand Down
5 changes: 3 additions & 2 deletions tests/test_statusbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from enlighten import EnlightenWarning, Justify

import tests
from tests import TestCase, MockManager, MockTTY, MockStatusBar, PY2, unittest


Expand Down Expand Up @@ -160,9 +161,9 @@ def test_reserve_fields(self):
with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
self.manager.status_bar(status_format=u'Stage: {stage}, Fill: {fill}', stage=1,
fields={'fill': 'Reserved field'})
self.assertRegex(__file__, warn.filename)
self.assertRegex(tests.__file__, warn.filename)

with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
self.manager.status_bar(status_format=u'Stage: {stage}, elapsed: {elapsed}', stage=1,
elapsed='Reserved field')
self.assertRegex(__file__, warn.filename)
self.assertRegex(tests.__file__, warn.filename)

0 comments on commit 021a256

Please sign in to comment.