commit ec9788b0fbca919e250d4c7ce1dc10dc4c8e9fb5 Author: Damian Johnson atagar@torproject.org Date: Tue Jul 19 09:43:20 2016 -0700
Proper handler chaining
Our tab completion handler was hardcoded to call the history handler. Fine if tab and history completion are always done together (as they are at the moment) but clearly a bug. Fixing this so any combination of handlers can be used. --- nyx/curses.py | 33 +++++++++++++++++++-------------- test/subwindow.py | 14 ++++++++------ 2 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/nyx/curses.py b/nyx/curses.py index 3767cd3..a6a8b2d 100644 --- a/nyx/curses.py +++ b/nyx/curses.py @@ -86,6 +86,7 @@ import collections import curses import curses.ascii import curses.textpad +import functools import os import threading
@@ -280,12 +281,15 @@ def str_input(x, y, initial_text = '', backlog = None, tab_completion = None): curses_subwindow.addstr(0, 0, initial_text[:width - 1])
textbox = curses.textpad.Textbox(curses_subwindow, insert_mode = True) - if tab_completion is not None: - user_input = textbox.edit(lambda key: _handle_tab_completion(textbox, key, backlog, tab_completion)).strip() - elif backlog is not None: - user_input = textbox.edit(lambda key: _handle_history_key(textbox, key, backlog)).strip() - else: - user_input = textbox.edit(lambda key: _handle_key(textbox, key)).strip() + handler = _handle_key + + if backlog: + handler = functools.partial(_handle_history_key, handler, backlog) + + if tab_completion: + handler = functools.partial(_handle_tab_completion, handler, tab_completion) + + user_input = textbox.edit(lambda key: handler(textbox, key)).strip()
try: curses.curs_set(0) # hide cursor @@ -328,14 +332,15 @@ def _handle_key(textbox, key): return key
-def _handle_history_key(textbox, key, backlog): +def _handle_history_key(next_handler, backlog, textbox, key): """ Handles history validation. When the up/down arrow keys are pressed, the relative previous/next commands are shown.
+ :param func next_handler: handler to invoke after this + :param list backlog: backlog of all previous commands entered :param Textbox textbox: current textbox context :param int key: key pressed - :param list backlog: backlog of all previous commands entered
:returns: **None** if up/down arrow key is pressed or calls function to write key to the textbox @@ -370,19 +375,19 @@ def _handle_history_key(textbox, key, backlog): HISTORY_DICT['selection_index'] = new_selection return None
- return _handle_key(textbox, key) + return next_handler(textbox, key)
-def _handle_tab_completion(textbox, key, backlog, tab_completion): +def _handle_tab_completion(next_handler, tab_completion, textbox, key): """ Handles tab completion. If the tab key is pressed, the current textbox contents are checked for probable commands.
- :param Textbox textbox: current textbox context - :param int key: key pressed - :param list backlog: backlog of all previous commands entered + :param func next_handler: handler to invoke after this :param Autocompleter.matches tab_completion: function to suggest probable commands based on current content + :param Textbox textbox: current textbox context + :param int key: key pressed
:returns: **None** when tab key is pressed or calls function to handle history validation @@ -409,7 +414,7 @@ def _handle_tab_completion(textbox, key, backlog, tab_completion):
return None
- return _handle_history_key(textbox, key, backlog) + return next_handler(textbox, key)
def curses_attr(*attributes): diff --git a/test/subwindow.py b/test/subwindow.py index 3f58278..559c451 100644 --- a/test/subwindow.py +++ b/test/subwindow.py @@ -68,6 +68,8 @@ EXPECTED_SCROLLBAR_BOTTOM = """ -+ """.strip()
+NO_OP_HANDLER = lambda key, textbox: None +
class TestCurses(unittest.TestCase): @require_curses @@ -156,21 +158,20 @@ class TestCurses(unittest.TestCase): key_pressed = ord('a') self.assertEqual(key_pressed, nyx.curses._handle_key(textbox, key_pressed))
- @patch('nyx.curses._handle_key') - def test_handle_history_key(self, mock_handle_key): + def test_handle_history_key(self): backlog = ['GETINFO version'] dimensions = (40, 80)
textbox = Mock() textbox.win.getyx.return_value = dimensions - self.assertIsNone(nyx.curses._handle_history_key(textbox, curses.KEY_UP, [])) + 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(textbox, curses.KEY_UP, backlog) + 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) @@ -178,7 +179,8 @@ class TestCurses(unittest.TestCase): self.assertEqual(expected_move_call, textbox.win.move.call_args)
textbox = Mock() - nyx.curses._handle_history_key(textbox, curses.KEY_LEFT, []) + mock_handle_key = Mock() + nyx.curses._handle_history_key(mock_handle_key, [], textbox, curses.KEY_LEFT) self.assertTrue(mock_handle_key.called)
@patch('nyx.curses._handle_history_key') @@ -193,7 +195,7 @@ class TestCurses(unittest.TestCase): textbox.win.move = Mock() tab_completion = Mock() tab_completion.return_value = [tab_completion_content] - nyx.curses._handle_tab_completion(textbox, 9, [], tab_completion) + nyx.curses._handle_tab_completion(NO_OP_HANDLER, tab_completion, textbox, 9) self.assertTrue(textbox.win.clear.called) expected_addstr_call = call(dimensions[0], 0, tab_completion_content) self.assertEqual(expected_addstr_call, textbox.win.addstr.call_args)