[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