diff --git a/keymap_drawer/config.py b/keymap_drawer/config.py index e8bacff..fce9e9a 100644 --- a/keymap_drawer/config.py +++ b/keymap_drawer/config.py @@ -222,18 +222,26 @@ class ParseConfig(BaseSettings, env_prefix="KEYMAP_", extra="ignore"): """Configuration settings related to parsing QMK/ZMK keymaps.""" class ModifierFnMap(BaseModel): - """Mapping to replace modifiers in modifier functions with the given string.""" - - left_ctrl: str = "C" - right_ctrl: str = "C" - left_shift: str = "S" - right_shift: str = "S" - left_alt: str = "A" # Alt/Opt - right_alt: str = "A" # Alt/Opt/AltGr - left_gui: str = "G" # Cmd/Win - right_gui: str = "G" # Cmd/Win - keycode_combiner: str = "{mods}+{key}" # pattern to join modifier functions with the modified keycode - modifier_combiner: str = "{mod_1}{mod_2}" # string to join multiple modifier function strings + """ + Mapping to replace modifiers in modifier functions with the given string. Includes `combiner` + patterns to determine how to format the result. Mod combinations in `mod_combinations` take + precedence over individual mod lookups. + """ + + left_ctrl: str = "Ctrl" + right_ctrl: str = "Ctrl" + left_shift: str = "Shift" + right_shift: str = "Shift" + left_alt: str = "Alt" # Alt/Opt + right_alt: str = "AltGr" # Alt/Opt/AltGr + left_gui: str = "Gui" # Cmd/Win + right_gui: str = "Gui" # Cmd/Win + keycode_combiner: str = "{mods}+ {key}" # pattern to join modifier functions with the modified keycode + mod_combiner: str = "{mod_1}+{mod_2}" # pattern to join multiple modifier function strings + special_combinations: dict[str, str] = { # special look-up for combinations of mods (mod order is ignored) + "left_ctrl+left_alt+left_gui+left_shift": "Hyper", + "left_ctrl+left_alt+left_shift": "Meh", + } # run C preprocessor on ZMK keymaps preprocess: bool = True diff --git a/keymap_drawer/parse/parse.py b/keymap_drawer/parse/parse.py index 3450e1f..06486ad 100644 --- a/keymap_drawer/parse/parse.py +++ b/keymap_drawer/parse/parse.py @@ -36,9 +36,13 @@ def __init__( self.conditional_layers: dict[int, list[int]] = {} # then-layer to if-layers mapping self.trans_key = LayoutKey.from_key_spec(self.cfg.trans_legend) self.raw_binding_map = self.cfg.raw_binding_map.copy() - self.modifier_fn_re = re.compile( + self._modifier_fn_re = re.compile( "(" + "|".join(re.escape(mod) for mod in self._modifier_fn_to_std) + r") *\( *(.*) *\)" ) + if (mod_map := self.cfg.modifier_fn_map) is not None: + self._mod_combs_lookup = { + frozenset(mods.split("+")): val for mods, val in mod_map.special_combinations.items() + } def parse_modifier_fns(self, keycode: str) -> tuple[str, list[str]]: """ @@ -50,7 +54,7 @@ def parse_modifier_fns(self, keycode: str) -> tuple[str, list[str]]: def strip_modifiers(keycode: str, current_mods: list[str] | None = None) -> tuple[str, list[str]]: if current_mods is None: current_mods = [] - if not (m := self.modifier_fn_re.fullmatch(keycode)): + if not (m := self._modifier_fn_re.fullmatch(keycode)): return keycode, current_mods return strip_modifiers(m.group(2), current_mods + self._modifier_fn_to_std[m.group(1)]) @@ -64,13 +68,16 @@ def format_modified_keys(self, key_str: str, modifiers: list[str]) -> str: if self.cfg.modifier_fn_map is None or not modifiers: return key_str - fn_map = self.cfg.modifier_fn_map.dict() - assert all( - mod in fn_map for mod in modifiers - ), f"Not all modifier functions in {modifiers} have a corresponding mapping in parse_config.modifier_fn_map" - fns_str = fn_map[modifiers[0]] - for mod in modifiers[1:]: - fns_str = self.cfg.modifier_fn_map.modifier_combiner.format(mod_1=fns_str, mod_2=fn_map[mod]) + if (combo_str := self._mod_combs_lookup.get(frozenset(modifiers))) is not None: + fns_str = combo_str + else: + fn_map = self.cfg.modifier_fn_map.dict() + assert all( + mod in fn_map for mod in modifiers + ), f"Not all modifier functions in {modifiers} have a corresponding mapping in parse_config.modifier_fn_map" + fns_str = fn_map[modifiers[0]] + for mod in modifiers[1:]: + fns_str = self.cfg.modifier_fn_map.mod_combiner.format(mod_1=fns_str, mod_2=fn_map[mod]) return self.cfg.modifier_fn_map.keycode_combiner.format(mods=fns_str, key=key_str) def update_layer_activated_from(