Commit 2f43b485f146

Vincent Demeester <vincent@sbr.pm>
2025-12-23 11:52:36
feat(moonlander): add comprehensive leader key system with 47 sequences
- Enable rapid code snippet insertion for Python, Emacs Lisp, and Nix - Improve leader combo ergonomics (Del+RAlt vs D+F) to prevent accidental triggers - Support language-specific patterns, imports, and application launchers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent 106f362
Changed files (3)
keyboards
keyboards/moonlander/config/config.h
@@ -31,6 +31,10 @@
 // Numword configuration
 #define NUMWORD_TIMEOUT 5000  // Time in ms before numword auto-disables (optional)
 
+// Leader key configuration
+#define LEADER_TIMEOUT 300  // Time in ms to wait for next key in leader sequence
+#define LEADER_PER_KEY_TIMING  // Each key in sequence gets its own timeout
+
 /* #define DOUBLE_TAP_SHIFT_TURNS_ON_CAPS_WORD 1 */
 /* #define CAPS_WORD_INVERT_ON_SHIFT 1 */
 
keyboards/moonlander/config/keymap.c
@@ -71,12 +71,146 @@ const key_override_t *key_overrides[] = {
 };
 
 void leader_start_user(void) {
+    // Visual feedback when leader mode activates (optional)
 }
 
 void leader_end_user(void) {
-    if (leader_sequence_one_key(KC_F)) {
-        // Leader, f => Types the below string
-        SEND_STRING("QMK is awesome.");
+    // ====== LAYOUT SWITCHING ======
+    // Leader + l + <key> for layout changes
+    if (leader_sequence_two_keys(KC_L, KC_B)) {
+        layer_move(BEPO);
+    } else if (leader_sequence_two_keys(KC_L, KC_E)) {
+        layer_move(ERGL);
+    } else if (leader_sequence_two_keys(KC_L, KC_Q)) {
+        layer_move(QWER);
+    }
+
+    // ====== DEVELOPMENT PATTERNS ======
+    // Leader + c + <key> for code snippets
+    else if (leader_sequence_two_keys(KC_C, KC_N)) {
+        SEND_STRING("nil");
+    } else if (leader_sequence_two_keys(KC_C, KC_E)) {
+        SEND_STRING("if err != nil {" SS_TAP(X_ENT) SS_TAP(X_TAB));
+    } else if (leader_sequence_two_keys(KC_C, KC_L)) {
+        SEND_STRING("console.log()");
+        SEND_STRING(SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_C, KC_P)) {
+        SEND_STRING("fmt.Println()");
+        SEND_STRING(SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_C, KC_F)) {
+        SEND_STRING("function() {}");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_C, KC_A)) {
+        SEND_STRING("() => {}");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    }
+
+    // ====== PERSONAL MACROS ======
+    // Leader + m + <key> for personal macros
+    else if (leader_sequence_two_keys(KC_M, KC_E)) {
+        SEND_STRING("vincent@sbr.pm"); // TODO: Update with your email
+    } else if (leader_sequence_two_keys(KC_M, KC_G)) {
+        SEND_STRING("Vincent Demeester <vincent@sbr.pm>"); // Git signature
+    } else if (leader_sequence_two_keys(KC_M, KC_S)) {
+        SEND_STRING("--" SS_TAP(X_ENT) "Vincent Demeester" SS_TAP(X_ENT) "vincent@sbr.pm");
+    } else if (leader_sequence_two_keys(KC_M, KC_T)) {
+        // ISO 8601 timestamp - you might want to use a more dynamic approach
+        SEND_STRING(SS_LCTL("u")); // This is a placeholder - QMK can't generate current date
+    }
+
+    // ====== PYTHON PATTERNS ======
+    // Leader + p + <key> for Python code snippets
+    else if (leader_sequence_two_keys(KC_P, KC_I)) {
+        SEND_STRING("if __name__ == \"__main__\":" SS_TAP(X_ENT) SS_TAP(X_TAB));
+    } else if (leader_sequence_two_keys(KC_P, KC_D)) {
+        SEND_STRING("def ():" SS_TAP(X_ENT) SS_TAP(X_TAB));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_P, KC_C)) {
+        SEND_STRING("class :" SS_TAP(X_ENT) SS_TAP(X_TAB));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_P, KC_P)) {
+        SEND_STRING("print(f\"\")");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_P, KC_T)) {
+        SEND_STRING("try:" SS_TAP(X_ENT) SS_TAP(X_TAB));
+        SEND_STRING(SS_TAP(X_ENT) SS_LSFT(SS_TAP(X_TAB)) "except Exception as e:" SS_TAP(X_ENT) SS_TAP(X_TAB));
+    } else if (leader_sequence_two_keys(KC_P, KC_W)) {
+        SEND_STRING("with open(\"\", \"r\") as f:" SS_TAP(X_ENT) SS_TAP(X_TAB));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    }
+
+    // ====== EMACS LISP PATTERNS ======
+    // Leader + e + <key> for Emacs Lisp snippets
+    else if (leader_sequence_two_keys(KC_E, KC_D)) {
+        SEND_STRING("(defun  ()" SS_TAP(X_ENT));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_E, KC_I)) {
+        SEND_STRING("(interactive)");
+    } else if (leader_sequence_two_keys(KC_E, KC_L)) {
+        SEND_STRING("(let ((");
+    } else if (leader_sequence_two_keys(KC_E, KC_S)) {
+        SEND_STRING("(setq  )");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_E, KC_M)) {
+        SEND_STRING("(message \"\")");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_E, KC_R)) {
+        SEND_STRING("(require ')");
+        SEND_STRING(SS_TAP(X_LEFT));
+    }
+
+    // ====== NIX PATTERNS ======
+    // Leader + n + <key> for Nix snippets
+    else if (leader_sequence_two_keys(KC_N, KC_F)) {
+        SEND_STRING("{ pkgs, ... }:");
+    } else if (leader_sequence_two_keys(KC_N, KC_L)) {
+        SEND_STRING("let" SS_TAP(X_ENT) SS_TAP(X_TAB) SS_TAP(X_ENT) "in" SS_TAP(X_ENT));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_UP));
+    } else if (leader_sequence_two_keys(KC_N, KC_W)) {
+        SEND_STRING("with pkgs; [" SS_TAP(X_ENT) SS_TAP(X_TAB) SS_TAP(X_ENT) "]");
+        SEND_STRING(SS_TAP(X_UP));
+    } else if (leader_sequence_two_keys(KC_N, KC_I)) {
+        SEND_STRING("inherit ;");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_N, KC_B)) {
+        SEND_STRING("buildInputs = [ ];");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_N, KC_P)) {
+        SEND_STRING("pkgs.writeShellScriptBin \"\" ''");
+        SEND_STRING(SS_TAP(X_ENT) SS_TAP(X_TAB) SS_TAP(X_ENT) "''");
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_UP) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    }
+
+    // ====== IMPORT PATTERNS ======
+    // Leader + i + <key> for common imports
+    else if (leader_sequence_two_keys(KC_I, KC_P)) {
+        SEND_STRING("import ");
+    } else if (leader_sequence_two_keys(KC_I, KC_F)) {
+        SEND_STRING("from  import ");
+        SEND_STRING(SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT) SS_TAP(X_LEFT));
+    } else if (leader_sequence_two_keys(KC_I, KC_N)) {
+        SEND_STRING("{ pkgs }: {" SS_TAP(X_ENT) SS_TAP(X_TAB) SS_TAP(X_ENT) "}");
+        SEND_STRING(SS_TAP(X_UP));
+    } else if (leader_sequence_two_keys(KC_I, KC_E)) {
+        SEND_STRING("(use-package " SS_TAP(X_ENT) SS_TAP(X_TAB));
+        SEND_STRING(SS_TAP(X_UP) SS_TAP(X_END));
+    }
+
+    // ====== APPLICATION SHORTCUTS ======
+    // Leader + a + <key> for application launches
+    // These use the configured niri keybindings
+    else if (leader_sequence_two_keys(KC_A, KC_D)) {
+        SEND_STRING(SS_LGUI("d")); // Mod+D for fuzzel (app launcher)
+    } else if (leader_sequence_two_keys(KC_A, KC_E)) {
+        SEND_STRING(SS_LGUI(SS_LSFT(SS_TAP(X_ENT)))); // Mod+Shift+Enter for Emacs client
+    } else if (leader_sequence_two_keys(KC_A, KC_T)) {
+        SEND_STRING(SS_LGUI(SS_TAP(X_ENT))); // Mod+Enter for Terminal (kitty)
+    } else if (leader_sequence_two_keys(KC_A, KC_J)) {
+        SEND_STRING(SS_LGUI(SS_LCTL("d"))); // Mod+Control+D for emoji picker (rofimoji)
+    } else if (leader_sequence_two_keys(KC_A, KC_V)) {
+        SEND_STRING(SS_LGUI(SS_LCTL("v"))); // Mod+Control+V for clipboard history (cliphist)
+    } else if (leader_sequence_two_keys(KC_A, KC_R)) {
+        SEND_STRING(SS_LGUI(SS_LSFT("d"))); // Mod+Shift+D for raffi launcher
     }
 }
 
@@ -101,8 +235,8 @@ enum combos {
 };
 
 const uint16_t PROGMEM combo_to_bepo[] = {LT(NAVI,KC_BSPC), OS_LSFT, COMBO_END};
-const uint16_t PROGMEM combo_to_ergol[] = {LT(NUMB,KC_SPC), LT(SYMB, KC_ENT), COMBO_END};
-const uint16_t PROGMEM combo_to_qwerty[] = {KC_DEL, KC_RALT, COMBO_END};
+const uint16_t PROGMEM combo_to_ergol[] = {KC_Q, KC_P, COMBO_END};  // Q+P opposite corners
+const uint16_t PROGMEM combo_to_qwerty[] = {LT(NUMB,KC_SPC), LT(SYMB, KC_ENT), COMBO_END};  // Numb+Symb
 const uint16_t PROGMEM combo_toggle_mouse[] = {KC_Q, KC_R, COMBO_END};
 
 const uint16_t PROGMEM combo_bepo_escape[] = {HM_ALT_R, HM_GUI_N, COMBO_END};
@@ -119,7 +253,7 @@ const uint16_t PROGMEM combo_qwe_minus[] = {KC_W, HM_ALT_S, COMBO_END};  // -
 const uint16_t PROGMEM combo_qwe_unds[] = {HM_CTL_F, KC_V, COMBO_END};   // _
 const uint16_t PROGMEM combo_qwe_equal[] = {HM_ALT_S, KC_X, COMBO_END};  // = 
 
-const uint16_t PROGMEM combo_qwe_leader[] = {HM_SFT_D, HM_CTL_F, COMBO_END}; // FIXME: change this most likely.
+const uint16_t PROGMEM combo_qwe_leader[] = {KC_DEL, KC_RALT, COMBO_END}; // Del+RAlt - right side thumb cluster, very deliberate
 
 const uint16_t PROGMEM combo_qwe_lprn[] = {KC_I, HM_SFT_K, COMBO_END};    // (
 const uint16_t PROGMEM combo_qwe_rprn[] = {HM_SFT_K, KC_COMM, COMBO_END}; // )
keyboards/moonlander/README.org
@@ -57,11 +57,90 @@
 
 * Usage
 
-- *Layout switching*: Bépo (Nav+Shift), ErgoL (Num+Sym), QWERTY (Del+RAlt)
+- *Layout switching*: Bépo (Nav+Shift), ErgoL (Q+P), QWERTY (Numb+Symb), or via leader sequences
 - *Numword*: Hold Space on Bépo layer (auto-disable after 5s)
-- *Leader*: Combo D+F on QWERTY
+- *Leader key*: Combo Del+RAlt (right thumb cluster, 300ms timeout per key)
 - *Mouse toggle*: Combo Q+R
 
+** Leader Key Sequences
+
+The leader key is activated by pressing Del+RAlt simultaneously (right thumb cluster). After activation, you have 300ms to press each subsequent key in the sequence.
+
+*** Layout Switching (Leader + L + key)
+| Sequence | Action         |
+|----------+----------------|
+| l b      | Switch to Bépo |
+| l e      | Switch to ErgoL|
+| l q      | Switch to QWERTY|
+
+*** Development Patterns - General (Leader + C + key)
+| Sequence | Output                    | Notes                          |
+|----------+---------------------------+--------------------------------|
+| c n      | nil                       | Go/common nil keyword          |
+| c e      | if err != nil {<enter><tab>| Go error handling pattern      |
+| c l      | console.log()             | Cursor positioned inside ()    |
+| c p      | fmt.Println()             | Cursor positioned inside ()    |
+| c f      | function() {}             | Cursor positioned inside {}    |
+| c a      | () => {}                  | Arrow function, cursor inside {}|
+
+*** Development Patterns - Python (Leader + P + key)
+| Sequence | Output                    | Notes                          |
+|----------+---------------------------+--------------------------------|
+| p i      | if __name__ == "__main__":| Python main block              |
+| p d      | def ():                   | Function definition, cursor at name |
+| p c      | class :                   | Class definition, cursor at name |
+| p p      | print(f"")                | f-string print, cursor inside quotes |
+| p t      | try:...except Exception as e: | Try-except block           |
+| p w      | with open("", "r") as f:  | File context manager, cursor at path |
+
+*** Development Patterns - Emacs Lisp (Leader + E + key)
+| Sequence | Output                    | Notes                          |
+|----------+---------------------------+--------------------------------|
+| e d      | (defun  ()                | Function definition, cursor at name |
+| e i      | (interactive)             | Interactive declaration        |
+| e l      | (let ((                   | Let binding                    |
+| e s      | (setq  )                  | Set variable, cursor at name   |
+| e m      | (message "")              | Message output, cursor inside quotes |
+| e r      | (require ')               | Require package, cursor after quote |
+
+*** Development Patterns - Nix (Leader + N + key)
+| Sequence | Output                    | Notes                          |
+|----------+---------------------------+--------------------------------|
+| n f      | { pkgs, ... }:            | Nix function signature         |
+| n l      | let...in                  | Let expression, cursor in body |
+| n w      | with pkgs; [...]          | With statement, cursor in list |
+| n i      | inherit ;                 | Inherit, cursor before semicolon |
+| n b      | buildInputs = [ ];        | buildInputs, cursor in list    |
+| n p      | pkgs.writeShellScriptBin ""| Shell script, cursor at name   |
+
+*** Import Patterns (Leader + I + key)
+| Sequence | Output                    | Notes                          |
+|----------+---------------------------+--------------------------------|
+| i p      | import                    | Python simple import           |
+| i f      | from  import              | Python from import, cursor at module |
+| i n      | { pkgs }: {...}           | Nix module definition          |
+| i e      | (use-package              | Emacs use-package declaration  |
+
+*** Personal Macros (Leader + M + key)
+| Sequence | Output                              | Notes                  |
+|----------+-------------------------------------+------------------------|
+| m e      | vincent@sbr.pm                      | Email address          |
+| m g      | Vincent Demeester <vincent@sbr.pm>  | Git signature format   |
+| m s      | --<enter>Vincent Demeester<enter>...| Email signature        |
+| m t      | <Ctrl+U>                            | Timestamp placeholder  |
+
+*** Application Shortcuts (Leader + A + key)
+| Sequence | Keybind           | Action                         |
+|----------+-------------------+--------------------------------|
+| a d      | Mod+D             | Launch fuzzel app launcher     |
+| a e      | Mod+Shift+Enter   | Launch Emacs client            |
+| a t      | Mod+Enter         | Launch Terminal (kitty)        |
+| a j      | Mod+Control+D     | Emoji picker (rofimoji)        |
+| a v      | Mod+Control+V     | Clipboard history (cliphist)   |
+| a r      | Mod+Shift+D       | Raffi launcher                 |
+
+*Note*: These shortcuts match your niri window manager configuration.
+
 * Development
 
 Files: =config/{config.h, keymap.c, layermodes.{c,h}, rules.mk}=, =go.sh=