From 08f336769fbfde1ac2bac17dfe9014a9c1cbd81e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 20 May 2017 11:41:21 +0530 Subject: [PATCH] Add tests for key mapping Also fix Alt+Special keys no generating correct codes --- kitty/keys.py | 66 ++++++++++++++++++++---------------- kitty_tests/keys.py | 81 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 28 deletions(-) create mode 100644 kitty_tests/keys.py diff --git a/kitty/keys.py b/kitty/keys.py index 4bcb6f6af..519fa1690 100644 --- a/kitty/keys.py +++ b/kitty/keys.py @@ -7,7 +7,28 @@ from .terminfo import key_as_bytes from .utils import base64_encode from .key_encoding import KEY_MAP -smkx_key_map = { + +def modify_key_bytes(keybytes, amt): + ans = bytearray(keybytes) + amt = str(amt).encode('ascii') + if ans[-1] == ord('~'): + return bytes(ans[:-1] + bytearray(b';' + amt + b'~')) + if ans[1] == ord('O'): + return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:]) + raise ValueError('Unknown key type') + + +def modify_complex_key(name, amt): + return modify_key_bytes(key_as_bytes(name), amt) + + +control_codes = {} +smkx_key_map = {} +alt_codes = {defines.GLFW_KEY_TAB: b'\033\t'} +shift_alt_codes = {defines.GLFW_KEY_TAB: key_as_bytes('kcbt')} +alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT) + +for kf, kn in { defines.GLFW_KEY_UP: 'kcuu1', defines.GLFW_KEY_DOWN: 'kcud1', defines.GLFW_KEY_LEFT: 'kcub1', @@ -18,12 +39,19 @@ smkx_key_map = { defines.GLFW_KEY_DELETE: 'kdch1', defines.GLFW_KEY_PAGE_UP: 'kpp', defines.GLFW_KEY_PAGE_DOWN: 'knp', -} -smkx_key_map = {k: key_as_bytes(v) for k, v in smkx_key_map.items()} +}.items(): + smkx_key_map[kf] = key_as_bytes(kn) + alt_codes[kf] = modify_complex_key(kn, 3) + shift_alt_codes[kf] = modify_complex_key(kn, 4) + control_codes[kf] = modify_complex_key(kn, 5) for f in range(1, 13): kf = getattr(defines, 'GLFW_KEY_F{}'.format(f)) - smkx_key_map[kf] = key_as_bytes('kf{}'.format(f)) -del f, kf + kn = 'kf{}'.format(f) + smkx_key_map[kf] = key_as_bytes(kn) + alt_codes[kf] = modify_complex_key(kn, 3) + shift_alt_codes[kf] = modify_complex_key(kn, 4) + control_codes[kf] = modify_complex_key(kn, 5) +del f, kf, kn smkx_key_map[defines.GLFW_KEY_ESCAPE] = b'\033' smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r' @@ -39,28 +67,13 @@ SHIFTED_KEYS = { defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'), } -control_codes = { +control_codes.update({ k: (1 + i, ) for i, k in enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1)) -} +}) -def rkey(name, a, b): - return bytearray(key_as_bytes(name).replace(a, b)) - - -control_codes[defines.GLFW_KEY_PAGE_UP] = rkey('kpp', b'~', b';5~') -control_codes[defines.GLFW_KEY_PAGE_DOWN] = rkey('knp', b'~', b';5~') -control_codes[defines.GLFW_KEY_DELETE] = rkey('kdch1', b'~', b';5~') -alt_codes = { - k: (0x1b, k) - for i, k in enumerate( - range(defines.GLFW_KEY_SPACE, defines.GLFW_KEY_RIGHT_BRACKET + 1) - ) -} -alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT) - rmkx_key_map = smkx_key_map.copy() rmkx_key_map.update({ defines.GLFW_KEY_UP: b'\033[A', @@ -70,9 +83,6 @@ rmkx_key_map.update({ defines.GLFW_KEY_HOME: b'\033[H', defines.GLFW_KEY_END: b'\033[F', }) -for sk in 'UP DOWN LEFT RIGHT HOME END'.split(): - sk = getattr(defines, 'GLFW_KEY_' + sk) - control_codes[sk] = rmkx_key_map[sk].replace(b'[', b'[1;5') cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map} @@ -132,7 +142,7 @@ def extended_key_event(key, scancode, mods, action): ).encode('ascii') -def interpret_key_event(key, scancode, mods, window, action): +def interpret_key_event(key, scancode, mods, window, action, get_localized_key=get_localized_key): screen = window.screen key = get_localized_key(key, scancode) if screen.extended_keyboard: @@ -145,8 +155,8 @@ def interpret_key_event(key, scancode, mods, window, action): # Map Ctrl-key to ascii control code data.extend(control_codes[key]) elif mods in alt_mods and key in alt_codes: - # Handled by interpret text event - pass + # Printable keys handled by interpret_text_event() + data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key]) else: key_map = get_key_map(screen) x = key_map.get(key) diff --git a/kitty_tests/keys.py b/kitty_tests/keys.py new file mode 100644 index 000000000..13d4cf415 --- /dev/null +++ b/kitty_tests/keys.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2016, Kovid Goyal + +from functools import partial + +import kitty.fast_data_types as defines +from kitty.keys import ( + interpret_key_event, modify_complex_key, modify_key_bytes, smkx_key_map +) + +from . import BaseTest + + +class DummyWindow: + + def __init__(self): + self.screen = self + self.extended_keyboard = False + self.cursor_key_mode = True + + +class TestParser(BaseTest): + + def test_modify_complex_key(self): + self.ae(modify_complex_key('kcuu1', 4), b'\033[1;4A') + self.ae(modify_complex_key('kcuu1', 3), b'\033[1;3A') + self.ae(modify_complex_key('kf5', 3), b'\033[15;3~') + self.assertRaises(ValueError, modify_complex_key, 'kri', 3) + + def test_interpret_key_event(self): + # test rmkx/smkx + w = DummyWindow() + + def k(expected, key, mods=0): + actual = interpret_key_event( + getattr(defines, 'GLFW_KEY_' + key), + 0, + mods, + w, + defines.GLFW_PRESS, + get_localized_key=lambda k, s: k + ) + self.ae(b'\033' + expected.encode('ascii'), actual) + + for ckm, mch in {True: 'O', False: '['}.items(): + w.cursor_key_mode = ckm + for name, ch in { + 'UP': 'A', + 'DOWN': 'B', + 'RIGHT': 'C', + 'LEFT': 'D', + 'HOME': 'H', + 'END': 'F', + }.items(): + k(mch + ch, name) + w.cursor_key_mode = True + + # test remaining special keys + for key, num in zip('INSERT DELETE PAGE_UP PAGE_DOWN'.split(), '2356'): + k('[' + num + '~', key) + for key, num in zip('1234', 'PQRS'): + k('O' + num, 'F' + key) + for key, num in zip(range(5, 13), (15, 17, 18, 19, 20, 21, 23, 24)): + k('[' + str(num) + '~', 'F{}'.format(key)) + + # test modifiers + SPECIAL_KEYS = 'UP DOWN RIGHT LEFT HOME END INSERT DELETE PAGE_UP PAGE_DOWN ' + for i in range(1, 13): + SPECIAL_KEYS += 'F{} '.format(i) + SPECIAL_KEYS = SPECIAL_KEYS.strip().split() + for mods, num in zip(('CONTROL', 'ALT', 'SHIFT+ALT'), '534'): + fmods = 0 + num = int(num) + for m in mods.split('+'): + fmods |= getattr(defines, 'GLFW_MOD_' + m) + km = partial(k, mods=fmods) + for key in SPECIAL_KEYS: + keycode = getattr(defines, 'GLFW_KEY_' + key) + base_key = smkx_key_map[keycode] + km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)