diff --git a/karaml/key_codes.py b/karaml/key_codes.py index 414cffb..e706a78 100644 --- a/karaml/key_codes.py +++ b/karaml/key_codes.py @@ -11,6 +11,7 @@ alias_modifiers = ["shift"] ALIASES = { "hyper": Alias("right_shift", ["right_command", "right_control", "right_option"]), + "☆": Alias("shift", ["command", "control", "option"]), "ultra": Alias("right_shift", ["right_command", "right_control", "right_option", "fn"]), "super": Alias("right_shift", ["right_option", "right_control"]), "enter": Alias("return_or_enter", None), @@ -121,12 +122,23 @@ "r": "control", "a": "option", "h": "shift", +} + - # Unicode symbols for modifiers - "⌘": "command", - "⌥": "option", - "⌃": "control", - "⇧": "shift", +UNICODE_MODS = { + "⌘": "g", + "⌥": "a", + "⌃": "r", + "⇧": "h", + "‹⌘": "m", + "⌘›": "M", + "‹⌥": "o", + "⌥›": "O", + "‹⌃": "c", + "⌃›": "C", + "‹⇧": "s", + "⇧›": "S", + "☆": "garh", } KEY_CODE = [ diff --git a/karaml/map_translator.py b/karaml/map_translator.py index 4f6a63b..3522528 100644 --- a/karaml/map_translator.py +++ b/karaml/map_translator.py @@ -9,7 +9,9 @@ check_and_validate_str_as_dict, ) from karaml.exceptions import invalidKey, invalidSoftFunct -from karaml.key_codes import KEY_CODE_REF_LISTS, MODIFIERS, PSEUDO_FUNCS +from karaml.key_codes import (KEY_CODE_REF_LISTS, MODIFIERS, PSEUDO_FUNCS, + UNICODE_MODS, + ) KeyStruct = namedtuple("KeyStruct", ["key_type", "key_code", "modifiers"]) ModifiedKey = namedtuple("ModifiedKey", ["modifiers", "key"]) @@ -361,10 +363,42 @@ def translate_if_valid_keycode(usr_key: str, usr_map: str) -> KeyStruct: def parse_primary_key_and_mods(usr_key: str, usr_map) -> tuple[str, dict]: - if modded_key := is_modded_key(usr_key): - modifiers: dict = get_modifiers(modded_key.modifiers, usr_map) - return modded_key.key, modifiers - return usr_key, {} + modded_key = is_modded_key(usr_key) + if not modded_key: + return usr_key, {} + + # The modifiers should either be ascii or unicode, but not a mix + if bool(set(UNICODE_MODS.keys()) & set(modded_key.modifiers)): + modifiers_string: str = translate_unicode_mods(modded_key.modifiers) + else: + modifiers_string: str = modded_key.modifiers + modifiers: dict = get_modifiers(modifiers_string, usr_map) + return modded_key.key, modifiers + + +def translate_unicode_mods(modifiers: str) -> str: + """ + Translate unicode modifiers to their corresponding ascii modifier aliases + and return the translated string. + + Unlike the ascii modifiers, whose side is determined by whether they are + lower or upper case, unicode modifiers' side are determined by whether they + are prefixed or suffixed by a unicode 'arrow' character. This function + handles the three cases: prefix, suffix, and neither. + """ + translated_mods = "" + for i, char in enumerate(modifiers): + if char in ["‹", "›", " "]: + continue + if i > 0 and modifiers[i-1] == "‹": + translated_char = UNICODE_MODS["‹" + char] + elif i < len(modifiers) - 1 and modifiers[i + 1] == "›": + translated_char = UNICODE_MODS[char + "›"] + else: + translated_char = UNICODE_MODS[char] + translated_mods += translated_char + + return translated_mods def is_modded_key(mapping: str) -> ModifiedKey: @@ -377,7 +411,7 @@ def is_modded_key(mapping: str) -> ModifiedKey: a hyphen or a pipe). The modifiers may be enclosed in angle brackets. """ - expression = r"-]+)[|-]([^>]+)>?" + expression = r"[<:]?([^|>-]+)[|-]([^>]+)>?" if query := search(expression, mapping): modifiers, key = query.groups() modifiers = modifiers.replace(" ", "") @@ -385,7 +419,6 @@ def is_modded_key(mapping: str) -> ModifiedKey: return ModifiedKey(modifiers, key) - def get_modifiers(usr_mods: str, usr_map: str) -> dict: validate_mod_aliases(usr_mods) mods = {} @@ -409,7 +442,7 @@ def parse_chars_in_parens(string: str) -> tuple: """ in_parens: list = findall(r"\((.*?)\)", string) validate_optional_mod_sets(string, in_parens) - not_in_parens: list = findall(r"[\w+⌘⌥⌃⇧]+(?![^()]*\))", string) + not_in_parens: list = findall(r"[\w+]+(?![^()]*\))", string) in_parens = list(in_parens[0]) if in_parens else None not_in_parens = list(not_in_parens[0]) if not_in_parens else None diff --git a/test/test_map_translator.py b/test/test_map_translator.py index 4617a2d..12d19fa 100644 --- a/test/test_map_translator.py +++ b/test/test_map_translator.py @@ -27,19 +27,26 @@ def test_TranslatedMap(): def test_is_modded_key(): - valid = "" - invalid_modified = [ + valid_modified_keys = [ + "", "mods-primary", + "mods|primary", + "mods | primary", + "m o d s | primary", + ] + invalid_modified = [ + "mods:primary", "", - "", + "mods |", + "| primary", ] - valid_ModifiedKey: ModifiedKey = mp.is_modded_key(valid) - assert valid_ModifiedKey - assert type(valid_ModifiedKey) == ModifiedKey - assert valid_ModifiedKey.modifiers == "mods" - assert valid_ModifiedKey.key == "primary" + for valid in valid_modified_keys: + valid_ModifiedKey: ModifiedKey = mp.is_modded_key(valid) + assert valid_ModifiedKey + assert type(valid_ModifiedKey) == ModifiedKey + assert valid_ModifiedKey.modifiers == "mods" + assert valid_ModifiedKey.key == "primary" for inv_mod in invalid_modified: assert not mp.is_modded_key(inv_mod)