[tor-commits] [nyx/master] Rewrite backlog handling

atagar at torproject.org atagar at torproject.org
Sun Jul 31 23:32:41 UTC 2016


commit b6908b1fa5092eab691b261e06be34df1e1d3eb7
Author: Damian Johnson <atagar at torproject.org>
Date:   Sat Jul 23 10:50:07 2016 -0700

    Rewrite backlog handling
    
    Mutable function globals are code stink. They break thread safety and are just
    about never the right thing to do. The arm 1.4.5 HistoryValidator class was a
    lot closer to what we want...
    
      https://gitweb.torproject.org/nyx.git/tree/src/util/textInput.py?h=release#n103
    
    Cleaning that up and replacing our _handle_history_key() with it. This is
    invisible to callers (they're both private helpers). Improving our tests along
    the way too.
---
 nyx/curses.py     | 86 +++++++++++++++++++++++++------------------------------
 test/subwindow.py | 48 +++++++++++++++----------------
 2 files changed, 63 insertions(+), 71 deletions(-)

diff --git a/nyx/curses.py b/nyx/curses.py
index ecc2644..b33abd3 100644
--- a/nyx/curses.py
+++ b/nyx/curses.py
@@ -150,8 +150,6 @@ SPECIAL_KEYS = {
 
 Dimensions = collections.namedtuple('Dimensions', ['width', 'height'])
 
-HISTORY_DICT = {'selection_index': -1, 'custom_input': ''}
-
 
 def conf_handler(key, value):
   if key == 'features.colorOverride':
@@ -286,7 +284,7 @@ def str_input(x, y, initial_text = '', backlog = None, tab_completion = None):
     handler = _handle_key
 
     if backlog:
-      handler = functools.partial(_handle_history_key, handler, backlog)
+      handler = functools.partial(_TextBacklog(backlog)._handler, handler)
 
     if tab_completion:
       handler = functools.partial(_handle_tab_completion, handler, tab_completion)
@@ -335,50 +333,6 @@ def _handle_key(textbox, key):
     return key
 
 
-def _handle_history_key(next_handler, backlog, textbox, key):
-  """
-  Allows user to select previous inputs when pressing up/down.
-
-  :param func next_handler: handler to invoke after this
-  :param list backlog: backlog of all previous commands
-  :param Textbox textbox: current textbox context
-  :param int key: key pressed
-
-  :returns: **None** if up/down is pressed, otherwise invokes next handler
-  """
-
-  global HISTORY_DICT
-
-  if key in (curses.KEY_UP, curses.KEY_DOWN):
-    offset = 1 if key == curses.KEY_UP else -1
-    new_selection = HISTORY_DICT['selection_index'] + offset
-
-    new_selection = max(-1, new_selection)
-    new_selection = min(len(backlog) - 1, new_selection)
-
-    if HISTORY_DICT['selection_index'] == new_selection:
-      return None
-
-    if HISTORY_DICT['selection_index'] == -1:
-      HISTORY_DICT['custom_input'] = textbox.gather().strip()
-
-    if new_selection == -1:
-      new_input = HISTORY_DICT['custom_input']
-    else:
-      new_input = backlog[new_selection]
-
-    y, _ = textbox.win.getyx()
-    _, max_x = textbox.win.getmaxyx()
-    textbox.win.clear()
-    textbox.win.addstr(y, 0, new_input[:max_x - 1])
-    textbox.win.move(y, min(len(new_input), max_x - 1))
-
-    HISTORY_DICT['selection_index'] = new_selection
-    return None
-
-  return next_handler(textbox, key)
-
-
 def _handle_tab_completion(next_handler, tab_completion, textbox, key):
   """
   Allows user to tab complete commands if sufficient context is provided to
@@ -419,6 +373,44 @@ def _handle_tab_completion(next_handler, tab_completion, textbox, key):
   return next_handler(textbox, key)
 
 
+class _TextBacklog(object):
+  """
+  History backlog that allows the :func:`~nyx.curses.str_input` function to
+  scroll through prior inputs when pressing the up and down arrow keys.
+  """
+
+  def __init__(self, backlog = []):
+    self._backlog = backlog  # backlog contents, newest to oldest
+    self._selection = -1     # selected item, -1 if we're not on the backlog
+    self._custom_input = ''  # field's input prior to selecting a backlog item
+
+  def _handler(self, next_handler, textbox, key):
+    if key in (curses.KEY_UP, curses.KEY_DOWN):
+      if key == curses.KEY_UP:
+        new_selection = min(len(self._backlog) - 1, self._selection + 1)
+      else:
+        new_selection = max(-1, self._selection - 1)
+
+      if self._selection == new_selection:
+        return None
+
+      if self._selection == -1:
+        self._custom_input = textbox.gather().strip()  # save custom input
+
+      new_input = self._custom_input if new_selection == -1 else self._backlog[new_selection]
+
+      y, _ = textbox.win.getyx()
+      _, max_x = textbox.win.getmaxyx()
+      textbox.win.clear()
+      textbox.win.addstr(y, 0, new_input[:max_x - 1])
+      textbox.win.move(y, min(len(new_input), max_x - 1))
+      self._selection = new_selection
+
+      return None
+
+    return next_handler(textbox, key)
+
+
 def curses_attr(*attributes):
   """
   Provides encoding for the given curses text attributes.
diff --git a/test/subwindow.py b/test/subwindow.py
index b72aeb7..2df946e 100644
--- a/test/subwindow.py
+++ b/test/subwindow.py
@@ -174,30 +174,6 @@ class TestCurses(unittest.TestCase):
   def test_handle_key_when_resized(self):
     self.assertEqual(curses.ascii.BEL, nyx.curses._handle_key(_textbox(), 410))
 
-  def test_handle_history_key(self):
-    backlog = ['GETINFO version']
-
-    textbox = Mock()
-    textbox.win.getyx.return_value = DIMENSIONS
-    self.assertIsNone(nyx.curses._handle_history_key(NO_OP_HANDLER, [], textbox, curses.KEY_UP))
-
-    textbox = Mock()
-    textbox.win.getyx.return_value = DIMENSIONS
-    textbox.win.getmaxyx.return_value = DIMENSIONS
-    textbox.win.addstr = Mock()
-    textbox.win.move = Mock()
-    nyx.curses._handle_history_key(NO_OP_HANDLER, backlog, textbox, curses.KEY_UP)
-    self.assertTrue(textbox.win.clear.called)
-    expected_addstr_call = call(DIMENSIONS[0], 0, backlog[0])
-    self.assertEqual(expected_addstr_call, textbox.win.addstr.call_args)
-    expected_move_call = call(DIMENSIONS[0], len(backlog[0]))
-    self.assertEqual(expected_move_call, textbox.win.move.call_args)
-
-    textbox = Mock()
-    mock_handle_key = Mock()
-    nyx.curses._handle_history_key(mock_handle_key, [], textbox, curses.KEY_LEFT)
-    self.assertTrue(mock_handle_key.called)
-
   def test_handle_tab_completion_no_op(self):
     tab_completion = lambda txt_input: ['GETINFO version']
     result = nyx.curses._handle_tab_completion(NO_OP_HANDLER, tab_completion, _textbox(), ord('a'))
@@ -228,3 +204,27 @@ class TestCurses(unittest.TestCase):
     self.assertEqual(None, result)  # consumes input
     self.assertEquals(call(0, 8), textbox.win.move.call_args)  # move cursor to end
     self.assertEqual(call(0, 0, 'GETINFO '), textbox.win.addstr.call_args)
+
+  def test_text_backlog_no_op(self):
+    backlog = nyx.curses._TextBacklog(['GETINFO version'])
+    textbox = _textbox()
+
+    self.assertEqual(ord('a'), backlog._handler(NO_OP_HANDLER, textbox, ord('a')))
+    self.assertFalse(textbox.win.addstr.called)
+
+  def test_text_backlog_fills_history(self):
+    backlog = nyx.curses._TextBacklog(['GETINFO version'])
+    textbox = _textbox()
+
+    self.assertEqual(None, backlog._handler(NO_OP_HANDLER, textbox, curses.KEY_UP))
+    self.assertEqual(call(0, 0, 'GETINFO version'), textbox.win.addstr.call_args)
+
+  def test_text_backlog_remembers_custom_input(self):
+    backlog = nyx.curses._TextBacklog(['GETINFO version'])
+    textbox = _textbox(text = 'hello')
+
+    self.assertEqual(None, backlog._handler(NO_OP_HANDLER, textbox, curses.KEY_UP))
+    self.assertEqual(call(0, 0, 'GETINFO version'), textbox.win.addstr.call_args)
+
+    self.assertEqual(None, backlog._handler(NO_OP_HANDLER, textbox, curses.KEY_DOWN))
+    self.assertEqual(call(0, 0, 'hello'), textbox.win.addstr.call_args)





More information about the tor-commits mailing list