[tor-commits] [nyx/master] Merge text_input into panel

atagar at torproject.org atagar at torproject.org
Mon Feb 15 14:45:09 UTC 2016


commit bf58d639c25c5d7cf83ad840b68d716c59b81437
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Feb 14 12:58:30 2016 -0800

    Merge text_input into panel
    
    Our text_input module was primarily to support the control interpreter. We
    might re-introduce some of this in the future but for now only the
    BasicValidator is used by our codebase.
---
 nyx/util/__init__.py   |   2 -
 nyx/util/panel.py      |  80 +++++++++++++++++--
 nyx/util/text_input.py | 213 -------------------------------------------------
 3 files changed, 75 insertions(+), 220 deletions(-)

diff --git a/nyx/util/__init__.py b/nyx/util/__init__.py
index e32a816..1bc3d71 100644
--- a/nyx/util/__init__.py
+++ b/nyx/util/__init__.py
@@ -17,8 +17,6 @@ from nyx.util import log
 __all__ = [
   'log',
   'panel',
-  'text_input',
-  'tor_config',
   'tracker',
   'ui_tools',
 ]
diff --git a/nyx/util/panel.py b/nyx/util/panel.py
index bf16422..611a14d 100644
--- a/nyx/util/panel.py
+++ b/nyx/util/panel.py
@@ -9,7 +9,7 @@ import curses.ascii
 import curses.textpad
 from threading import RLock
 
-from nyx.util import text_input, ui_tools
+from nyx.util import ui_tools
 
 from stem.util import conf, log, str_tools
 
@@ -32,6 +32,8 @@ SPECIAL_KEYS = {
   'esc': 27,
 }
 
+PASS = -1
+
 
 def conf_handler(key, value):
   if key == 'features.torrc.maxLineWrap':
@@ -46,6 +48,76 @@ CONFIG = conf.config_dict('nyx', {
 HALT_ACTIVITY = False
 
 
+class BasicValidator(object):
+  """
+  Interceptor for keystrokes given to a textbox, doing the following:
+  - quits by setting the input to curses.ascii.BEL when escape is pressed
+  - stops the cursor at the end of the box's content when pressing the right
+    arrow
+  - home and end keys move to the start/end of the line
+  """
+
+  def validate(self, key, textbox):
+    """
+    Processes the given key input for the textbox. This may modify the
+    textbox's content, cursor position, etc depending on the functionality
+    of the validator. This returns the key that the textbox should interpret,
+    PASS if this validator doesn't want to take any action.
+
+    Arguments:
+      key     - key code input from the user
+      textbox - curses Textbox instance the input came from
+    """
+
+    result = self.handle_key(key, textbox)
+    return key if result == PASS else result
+
+  def handle_key(self, key, textbox):
+    y, x = textbox.win.getyx()
+
+    if curses.ascii.isprint(key) and x < textbox.maxx:
+      # Shifts the existing text forward so input is an insert method rather
+      # than replacement. The curses.textpad accepts an insert mode flag but
+      # this has a couple issues...
+      # - The flag is only available for Python 2.6+, before that the
+      #   constructor only accepted a subwindow argument as per:
+      #   https://trac.torproject.org/projects/tor/ticket/2354
+      # - The textpad doesn't shift text that has text attributes. This is
+      #   because keycodes read by textbox.win.inch() includes formatting,
+      #   causing the curses.ascii.isprint() check it does to fail.
+
+      current_input = textbox.gather()
+      textbox.win.addstr(y, x + 1, current_input[x:textbox.maxx - 1])
+      textbox.win.move(y, x)  # reverts cursor movement during gather call
+    elif key == 27:
+      # curses.ascii.BEL is a character codes that causes textpad to terminate
+
+      return curses.ascii.BEL
+    elif key == curses.KEY_HOME:
+      textbox.win.move(y, 0)
+      return None
+    elif key in (curses.KEY_END, curses.KEY_RIGHT):
+      msg_length = len(textbox.gather())
+      textbox.win.move(y, x)  # reverts cursor movement during gather call
+
+      if key == curses.KEY_END and msg_length > 0 and x < msg_length - 1:
+        # if we're in the content then move to the end
+
+        textbox.win.move(y, msg_length - 1)
+        return None
+      elif key == curses.KEY_RIGHT and x >= msg_length - 1:
+        # don't move the cursor if there's no content after it
+
+        return None
+    elif key == 410:
+      # if we're resizing the display during text entry then cancel it
+      # (otherwise the input field is filled with nonprintable characters)
+
+      return curses.ascii.BEL
+
+    return PASS
+
+
 class Panel(object):
   """
   Wrapper for curses subwindows. This hides most of the ugliness in common
@@ -555,7 +627,7 @@ class Panel(object):
 
     return x, y
 
-  def getstr(self, y, x, initial_text = '', text_format = None, max_width = None, validator = None):
+  def getstr(self, y, x, initial_text = '', text_format = None, max_width = None):
     """
     Provides a text field where the user can input a string, blocking until
     they've done so and returning the result. If the user presses escape then
@@ -572,7 +644,6 @@ class Panel(object):
       initial_text - starting text in this field
       text_format  - format used for the text
       max_width    - maximum width for the text field
-      validator    - custom TextInputValidator for handling keybindings
     """
 
     if not text_format:
@@ -610,8 +681,7 @@ class Panel(object):
 
     textbox = curses.textpad.Textbox(input_subwindow)
 
-    if not validator:
-      validator = text_input.BasicValidator()
+    validator = BasicValidator()
 
     textbox.win.attron(text_format)
     user_input = textbox.edit(lambda key: validator.validate(key, textbox)).strip()
diff --git a/nyx/util/text_input.py b/nyx/util/text_input.py
deleted file mode 100644
index 3172750..0000000
--- a/nyx/util/text_input.py
+++ /dev/null
@@ -1,213 +0,0 @@
-"""
-Provides input validators that provide text input with various capabilities.
-These can be chained together with the first matching validator taking
-precidence.
-"""
-
-import os
-import curses
-
-PASS = -1
-
-
-class TextInputValidator:
-  """
-  Basic interface for validators. Implementations should override the handle_key
-  method.
-  """
-
-  def __init__(self, next_validator = None):
-    self.next_validator = next_validator
-
-  def validate(self, key, textbox):
-    """
-    Processes the given key input for the textbox. This may modify the
-    textbox's content, cursor position, etc depending on the functionality
-    of the validator. This returns the key that the textbox should interpret,
-    PASS if this validator doesn't want to take any action.
-
-    Arguments:
-      key     - key code input from the user
-      textbox - curses Textbox instance the input came from
-    """
-
-    result = self.handle_key(key, textbox)
-
-    if result != PASS:
-      return result
-    elif self.next_validator:
-      return self.next_validator.validate(key, textbox)
-    else:
-      return key
-
-  def handle_key(self, key, textbox):
-    """
-    Process the given keycode with this validator, returning the keycode for
-    the textbox to process, and PASS if this doesn't want to modify it.
-
-    Arguments:
-      key     - key code input from the user
-      textbox - curses Textbox instance the input came from
-    """
-
-    return PASS
-
-
-class BasicValidator(TextInputValidator):
-  """
-  Interceptor for keystrokes given to a textbox, doing the following:
-  - quits by setting the input to curses.ascii.BEL when escape is pressed
-  - stops the cursor at the end of the box's content when pressing the right
-    arrow
-  - home and end keys move to the start/end of the line
-  """
-
-  def handle_key(self, key, textbox):
-    y, x = textbox.win.getyx()
-
-    if curses.ascii.isprint(key) and x < textbox.maxx:
-      # Shifts the existing text forward so input is an insert method rather
-      # than replacement. The curses.textpad accepts an insert mode flag but
-      # this has a couple issues...
-      # - The flag is only available for Python 2.6+, before that the
-      #   constructor only accepted a subwindow argument as per:
-      #   https://trac.torproject.org/projects/tor/ticket/2354
-      # - The textpad doesn't shift text that has text attributes. This is
-      #   because keycodes read by textbox.win.inch() includes formatting,
-      #   causing the curses.ascii.isprint() check it does to fail.
-
-      current_input = textbox.gather()
-      textbox.win.addstr(y, x + 1, current_input[x:textbox.maxx - 1])
-      textbox.win.move(y, x)  # reverts cursor movement during gather call
-    elif key == 27:
-      # curses.ascii.BEL is a character codes that causes textpad to terminate
-
-      return curses.ascii.BEL
-    elif key == curses.KEY_HOME:
-      textbox.win.move(y, 0)
-      return None
-    elif key in (curses.KEY_END, curses.KEY_RIGHT):
-      msg_length = len(textbox.gather())
-      textbox.win.move(y, x)  # reverts cursor movement during gather call
-
-      if key == curses.KEY_END and msg_length > 0 and x < msg_length - 1:
-        # if we're in the content then move to the end
-
-        textbox.win.move(y, msg_length - 1)
-        return None
-      elif key == curses.KEY_RIGHT and x >= msg_length - 1:
-        # don't move the cursor if there's no content after it
-
-        return None
-    elif key == 410:
-      # if we're resizing the display during text entry then cancel it
-      # (otherwise the input field is filled with nonprintable characters)
-
-      return curses.ascii.BEL
-
-    return PASS
-
-
-class HistoryValidator(TextInputValidator):
-  """
-  This intercepts the up and down arrow keys to scroll through a backlog of
-  previous commands.
-  """
-
-  def __init__(self, command_backlog = [], next_validator = None):
-    TextInputValidator.__init__(self, next_validator)
-
-    # contents that can be scrolled back through, newest to oldest
-
-    self.command_backlog = command_backlog
-
-    # selected item from the backlog, -1 if we're not on a backlog item
-
-    self.selection_index = -1
-
-    # the fields input prior to selecting a backlog item
-
-    self.custom_input = ''
-
-  def handle_key(self, key, textbox):
-    if key in (curses.KEY_UP, curses.KEY_DOWN):
-      offset = 1 if key == curses.KEY_UP else -1
-      new_selection = self.selection_index + offset
-
-      # constrains the new selection to valid bounds
-
-      new_selection = max(-1, new_selection)
-      new_selection = min(len(self.command_backlog) - 1, new_selection)
-
-      # skips if this is a no-op
-
-      if self.selection_index == new_selection:
-        return None
-
-      # saves the previous input if we weren't on the backlog
-
-      if self.selection_index == -1:
-        self.custom_input = textbox.gather().strip()
-
-      if new_selection == -1:
-        new_input = self.custom_input
-      else:
-        new_input = self.command_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_index = new_selection
-      return None
-
-    return PASS
-
-
-class TabCompleter(TextInputValidator):
-  """
-  Provides tab completion based on the current input, finishing if there's only
-  a single match. This expects a functor that accepts the current input and
-  provides matches.
-  """
-
-  def __init__(self, completer, next_validator = None):
-    TextInputValidator.__init__(self, next_validator)
-
-    # functor that accepts a string and gives a list of matches
-
-    self.completer = completer
-
-  def handle_key(self, key, textbox):
-    # Matches against the tab key. The ord('\t') is nine, though strangely none
-    # of the curses.KEY_*TAB constants match this...
-
-    if key == 9:
-      current_contents = textbox.gather().strip()
-      matches = self.completer(current_contents)
-      new_input = None
-
-      if len(matches) == 1:
-        # only a single match, fill it in
-        new_input = matches[0]
-      elif len(matches) > 1:
-        # looks for a common prefix we can complete
-        common_prefix = os.path.commonprefix(matches)  # weird that this comes from path...
-
-        if common_prefix != current_contents:
-          new_input = common_prefix
-
-        # TODO: somehow display matches... this is not gonna be fun
-
-      if new_input:
-        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))
-
-      return None
-
-    return PASS





More information about the tor-commits mailing list