From 3eb9fe7163f7974699c1ca17f865c0ffd17077ac Mon Sep 17 00:00:00 2001 From: Ananya Timalsina <84459091+ananyatimalsina@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:04:56 +0100 Subject: [PATCH] feat(nix): add keymode support for modal keybindings --- nix/hm-modules.nix | 14 +++++++++++ nix/lib.nix | 59 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/nix/hm-modules.nix b/nix/hm-modules.nix index 2c450f61..81cec1e8 100644 --- a/nix/hm-modules.nix +++ b/nix/hm-modules.nix @@ -107,6 +107,9 @@ in This option uses a structured format that is converted to Mango's configuration syntax. Nested attributes are flattened with underscore separators. For example: `animation.duration_open = 400` becomes `animation_duration_open = 400` + + Keymodes (submaps) are supported via the special `keymode` attribute. Each keymode + is a nested attribute set under `keymode` that contains its own bindings. ::: ''; example = lib.literalExpression '' @@ -139,12 +142,23 @@ in "SUPER,r,reload_config" "Alt,space,spawn,rofi -show drun" "Alt,Return,spawn,foot" + "ALT,R,setkeymode,resize" # Enter resize mode ]; tagrule = [ "id:1,layout_name:tile" "id:2,layout_name:scroller" ]; + + # Keymodes (submaps) for modal keybindings + keymode = { + resize = { + bind = [ + "NONE,Left,resizewin,-10,0" + "NONE,Escape,setkeymode,default" + ]; + }; + }; } ''; }; diff --git a/nix/lib.nix b/nix/lib.nix index 406d94ae..9dfd2ff6 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -6,6 +6,7 @@ let foldl generators partition + removeAttrs ; inherit (lib.strings) @@ -136,6 +137,39 @@ let tagrule = id:2,layout_name:scroller ``` + ## Using keymodes (submaps) + + ```nix + let + config = { + bind = [ + "SUPER,Q,killclient" + "ALT,R,setkeymode,resize" + ]; + keymode = { + resize = { + bind = [ + "NONE,Left,resizewin,-10,0" + "NONE,Right,resizewin,10,0" + "NONE,Escape,setkeymode,default" + ]; + }; + }; + }; + in lib.toMango {} config + ``` + + **Output:** + ``` + bind = SUPER,Q,killclient + bind = ALT,R,setkeymode,resize + + keymode = resize + bind = NONE,Left,resizewin,-10,0 + bind = NONE,Right,resizewin,10,0 + bind = NONE,Escape,setkeymode,default + ``` + ::: */ toMango = @@ -156,9 +190,28 @@ let indent = ""; # No indent, since we don't have nesting }; + # Extract keymode definitions if they exist + keymodes = attrs.keymode or { }; + attrsWithoutKeymodes = removeAttrs attrs [ "keymode" ]; + + # Generate keymode blocks + # Format: keymode=name\nbind=...\nbind=...\n + mkKeymodeBlock = + name: modeAttrs: + let + modeCommands = flattenAttrs (p: k: "${p}_${k}") modeAttrs; + in + "keymode = ${name}\n${mkCommands modeCommands}"; + + keymodeBlocks = + if keymodes == { } then + "" + else + "\n" + concatMapStrings (name: mkKeymodeBlock name keymodes.${name} + "\n") (attrNames keymodes); + # Flatten the attrset, combining keys in a "path" like `"a_b_c" = "x"`. # Uses `flattenAttrs` with an underscore separator. - commands = flattenAttrs (p: k: "${p}_${k}") attrs; + commands = flattenAttrs (p: k: "${p}_${k}") attrsWithoutKeymodes; # General filtering function to check if a key starts with any prefix in a given list. filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; @@ -174,11 +227,13 @@ let regularCommands = removeAttrs remainingCommands result2.right; in # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + # Keymodes are appended at the end. concatMapStrings mkCommands [ topCommands regularCommands bottomCommands - ]; + ] + + keymodeBlocks; in toMango' attrs;