Repository: toejough/ugetch

Issue:
implement arrow key support

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index ae7af94..3b949a3 100644
--- a/README.rst
+++ b/README.rst
@@ -26,11 +26,9 @@ getkey
 
 Gets a "key" from the ``infile`` and returns it to the user, where "key" is a character, glyph, or string describing a control key.
 
-Currently supports parsing ASCII, UTF-8, and tab.
-Any other keys entered (such as arrow keys, UTF-16, etc) will result in returning individual bytes, one at a time.
-
-The tab key is represented as a string: 'TAB'.  This is not a "character", but it *is* a "key".  The implementation
-of keys may change in the future, but for now this seems to be simple and clear.
+Currently supports parsing ASCII, UTF-8, and some `special characters`_ as listed below.
+Any other keys entered will result in returning individual bytes, one at a time.
+*Pull requests adding special key support are welcome*
 
 Parameters:
 
@@ -40,6 +38,21 @@ Returns:
 
 * ``key`` - a string value corresponding to the key read in from the TTY ('a', 'B', '-', '☢', 'TAB', etc).
 
+Special Characters
+==================
+The following special characters are supported for VT220-like terminals.  This is all I personally have to test on,
+but pull reqeusts with additional support are welcome.  Issue #14 will make integration of new keys easier.
+
+Currently, special keys are supported by returning their name as a string, instead of a single character or glyph, as ASCII and UTF-8 do.
+This is not a "character", but it *is* a "key".  The implementation
+of keys may change in the future, but for now this seems to be simple and clear.
+
+* TAB
+* UP
+* DOWN
+* LEFT
+* RIGHT
+
 contribution
 ============
 
diff --git a/ugetch/__init__.py b/ugetch/__init__.py
index b615ca3..8d714da 100644
--- a/ugetch/__init__.py
+++ b/ugetch/__init__.py
@@ -65,11 +65,42 @@ def tab_parser(infile):
     return key
 
 
+def arrow_parser(infile):
+    '''Parse arrow keys'''
+    first_byte = _get_byte(infile)
+    key = None
+    if first_byte == 27:
+        # escape.  Check for bracket
+        second_byte = _get_byte(infile)
+        if second_byte == 91:
+            # final byte
+            final_byte = _get_byte(infile)
+            if final_byte == 65:
+                key = 'UP'
+            elif final_byte == 66:
+                key = 'DOWN'
+            elif final_byte == 67:
+                key = 'RIGHT'
+            elif final_byte == 68:
+                key = 'LEFT'
+            else:
+                _put_byte(first_byte)
+                _put_byte(second_byte)
+                _put_byte(final_byte)
+        else:
+            _put_byte(first_byte)
+            _put_byte(second_byte)
+    else:
+        _put_byte(first_byte)
+    return key
+
+
 # [ Global ]
 _DEFAULT=object()  # enable dynamic defaults
-# Tab needs to go before the ASCII parser, because it is technically in the ASCII range,
-#  and would be sucked in raw by the ASCII parser.
-_KEY_PARSERS=[tab_parser, ascii_parser, utf8_parser]  # ways to parse keys from byte lists
+# Special key parsers need to go before the ASCII parser, because
+#   their first byte is generally in the ASCII range,
+#   and would be sucked in raw by the ASCII parser.
+_KEY_PARSERS=[tab_parser, arrow_parser, ascii_parser, utf8_parser]  # ways to parse keys from byte lists
 _BYTES=[]  # byte buffer from the input file
 
 


Test patch:
diff --git a/test_ugetch.py b/test_ugetch.py
index 072b7bf..498adbd 100644
--- a/test_ugetch.py
+++ b/test_ugetch.py
@@ -34,7 +34,7 @@ def assert_expected(p, expected):
     '''Assert that we get the expected value'''
     index = p.expect_exact([expected, pexpect.EOF, pexpect.TIMEOUT])
     assert index == 0, "did not find expected ({}) in output ({})".format(
-        value, p.before
+        expected, p.before
     )
 
 
@@ -44,7 +44,7 @@ def in_vs_out(input_values):
     p = start_cli_shim()
     # test each char
     for value in input_values:
-        p.expect_exact("hit a key to print its representation: ")
+        assert_expected(p, "hit a key to print its representation: ")
         p.send(value)
         assert_expected(p, value)
 
@@ -74,6 +74,24 @@ def test_getch_tab():
     assert_expected(p, 'TAB')
 
 
+def test_getch_arrows():
+    # get shim
+    p = start_cli_shim()
+    # test each arrow
+    KEY_UP = '\x1b[A'
+    KEY_DOWN = '\x1b[B'
+    KEY_RIGHT = '\x1b[C'
+    KEY_LEFT = '\x1b[D'
+    p.send(KEY_UP)
+    assert_expected(p, 'UP')
+    p.send(KEY_DOWN)
+    assert_expected(p, 'DOWN')
+    p.send(KEY_LEFT)
+    assert_expected(p, 'LEFT')
+    p.send(KEY_RIGHT)
+    assert_expected(p, 'RIGHT')
+
+
 if __name__ == '__main__':
     import cProfile
     cProfile.run("test_getch_ascii()")