summary refs log tree commit diff
path: root/docs
diff options
context:
space:
mode:
authorPascal Getreuer <50221757+getreuer@users.noreply.github.com>2023-05-20 05:35:06 -0700
committerGitHub <noreply@github.com>2023-05-20 22:35:06 +1000
commit3993b15f054265071730cdb450f43457dcf4c64a (patch)
tree61c5b980ed14428bae3c0278c27937dbb5d33627 /docs
parente1766df185869d8a591228d37f3f7b6d5b4049b4 (diff)
[Core] Add Repeat Key ("repeat last key") as a core feature. (#19700)
Co-authored-by: casuanoob <96005765+casuanoob@users.noreply.github.com>
Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/_summary.md1
-rw-r--r--docs/feature_repeat_key.md457
-rw-r--r--docs/ja/_summary.md1
-rw-r--r--docs/keycodes.md9
-rw-r--r--docs/zh-cn/_summary.md1
5 files changed, 469 insertions, 0 deletions
diff --git a/docs/_summary.md b/docs/_summary.md
index ce579cb071..3d9bde6b17 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -70,6 +70,7 @@
     * [Macros](feature_macros.md)
     * [Mouse Keys](feature_mouse_keys.md)
     * [Programmable Button](feature_programmable_button.md)
+    * [Repeat Key](feature_repeat_key.md)
     * [Space Cadet Shift](feature_space_cadet.md)
     * [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md)
 
diff --git a/docs/feature_repeat_key.md b/docs/feature_repeat_key.md
new file mode 100644
index 0000000000..6fa8a724ef
--- /dev/null
+++ b/docs/feature_repeat_key.md
@@ -0,0 +1,457 @@
+# Repeat Key
+
+The Repeat Key performs the action of the last pressed key. Tapping the Repeat
+Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for
+typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd>
+can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is
+potentially faster and more comfortable. The Repeat Key is also useful for
+hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word. 
+
+Repeat Key remembers mods that were active with the last key press. These mods
+are combined with any additional mods while pressing the Repeat Key. If the last
+press key was <kbd>Ctrl</kbd> + <kbd>Z</kbd>, then <kbd>Shift</kbd> +
+<kbd>Repeat</kbd> performs Ctrl + Shift + `Z`.
+
+## How do I enable Repeat Key
+
+In your `rules.mk`, add:
+
+```make
+REPEAT_KEY_ENABLE = yes
+```
+
+Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short
+alias `QK_REP`). Optionally, use the keycode `QK_ALT_REPEAT_KEY` (short alias
+`QK_AREP`) on another key.
+
+## Keycodes
+
+|Keycode                |Aliases  |Description                          |
+|-----------------------|---------|-------------------------------------|
+|`QK_REPEAT_KEY`        |`QK_REP` |Repeat the last pressed key          |
+|`QK_ALT_REPEAT_KEY`    |`QK_AREP`|Perform alternate of the last key    |
+
+## Alternate Repeating
+
+The Alternate Repeat Key performs the "alternate" action of the last pressed key
+if it is defined. By default, Alternate Repeat is defined for navigation keys to
+act in the reverse direction. When the last key is the common "select by word"
+hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl +
+Shift + Left Arrow, which together with the Repeat Key enables convenient
+selection by words in either direction.
+
+Alternate Repeat is enabled with the Repeat Key by default. Optionally, to
+reduce firmware size, Alternate Repeat may be disabled by adding in config.h:
+
+```c
+#define NO_ALT_REPEAT_KEY
+```
+
+The following alternate keys are defined by default. See
+`get_alt_repeat_key_keycode_user()` below for how to change or add to these
+definitions. Where it makes sense, these definitions also include combinations 
+with mods, like Ctrl + Left &harr; Ctrl + Right Arrow.
+
+**Navigation** 
+
+|Keycodes                           |Description                        |
+|-----------------------------------|-----------------------------------|
+|`KC_LEFT` &harr; `KC_RGHT`         | Left &harr; Right Arrow           |
+|`KC_UP` &harr; `KC_DOWN`           | Up &harr; Down Arrow              |
+|`KC_HOME` &harr; `KC_END`          | Home &harr; End                   |
+|`KC_PGUP` &harr; `KC_PGDN`         | Page Up &harr; Page Down          |
+|`KC_MS_L` &harr; `KC_MS_R`         | Mouse Cursor Left &harr; Right    |
+|`KC_MS_U` &harr; `KC_MS_D`         | Mouse Cursor Up &harr; Down       |
+|`KC_WH_L` &harr; `KC_WH_R`         | Mouse Wheel Left &harr; Right     |
+|`KC_WH_U` &harr; `KC_WH_D`         | Mouse Wheel Up &harr; Down        |
+
+**Misc** 
+
+|Keycodes                           |Description                        |
+|-----------------------------------|-----------------------------------|
+|`KC_BSPC` &harr; `KC_DEL`          | Backspace &harr; Delete           |
+|`KC_LBRC` &harr; `KC_RBRC`         | `[` &harr; `]`                    |
+|`KC_LCBR` &harr; `KC_RCBR`         | `{` &harr; `}`                    |
+
+**Media** 
+
+|Keycodes                           |Description                        |
+|-----------------------------------|-----------------------------------|
+|`KC_WBAK` &harr; `KC_WFWD`         | Browser Back &harr; Forward       |
+|`KC_MNXT` &harr; `KC_MPRV`         | Next &harr; Previous Media Track  |
+|`KC_MFFD` &harr; `KC_MRWD`         | Fast Forward &harr; Rewind Media  |
+|`KC_VOLU` &harr; `KC_VOLD`         | Volume Up &harr; Down             |
+|`KC_BRIU` &harr; `KC_BRID`         | Brightness Up &harr; Down         |
+
+**Hotkeys in Vim, Emacs, and other programs**
+
+|Keycodes                           |Description                        |
+|-----------------------------------|-----------------------------------|
+|mod + `KC_F` &harr; mod + `KC_B`   | Forward &harr; Backward           |
+|mod + `KC_D` &harr; mod + `KC_U`   | Down &harr; Up                    |
+|mod + `KC_N` &harr; mod + `KC_P`   | Next &harr; Previous              |
+|mod + `KC_A` &harr; mod + `KC_E`   | Home &harr; End                   |
+|mod + `KC_O` &harr; mod + `KC_I`   | Vim jump list Older &harr; Newer  |
+|`KC_J` &harr; `KC_K`               | Down &harr; Up                    |
+|`KC_H` &harr; `KC_L`               | Left &harr; Right                 |
+|`KC_W` &harr; `KC_B`               | Forward &harr; Backward by Word   |
+
+(where above, "mod" is Ctrl, Alt, or GUI)
+
+
+## Defining alternate keys
+
+Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate"
+for additional keys or override the default definitions. For example, to define
+Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in
+keymap.c:
+
+```c
+uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
+    if ((mods & MOD_MASK_CTRL)) {  // Was Ctrl held?
+        switch (keycode) {
+            case KC_Y: return C(KC_Z);  // Ctrl + Y reverses to Ctrl + Z.
+            case KC_Z: return C(KC_Y);  // Ctrl + Z reverses to Ctrl + Y.
+        }
+    }
+
+    return KC_TRNS;  // Defer to default definitions.
+}
+```
+
+The `keycode` and `mods` args are the keycode and mods that were active with the
+last pressed key. The meaning of the return value from this function is:
+
+* `KC_NO` &ndash; do nothing (any predefined alternate key is not used);
+* `KC_TRNS` &ndash; use the default alternate key if it exists;
+* anything else &ndash; use the specified keycode. Any keycode may be returned
+  as an alternate key, including custom keycodes.
+
+Another example, defining Shift + Tab as the alternate of Tab, and vice versa:
+
+```c
+uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
+    bool shifted = (mods & MOD_MASK_SHIFT);  // Was Shift held?
+    switch (keycode) {
+        case KC_TAB:
+            if (shifted) {        // If the last key was Shift + Tab,
+                return KC_TAB;    // ... the reverse is Tab.
+            } else {              // Otherwise, the last key was Tab,
+                return S(KC_TAB); // ... and the reverse is Shift + Tab.
+            }
+    }
+
+    return KC_TRNS;
+}
+```
+
+#### Eliminating SFBs
+
+Alternate Repeat can be configured more generally to perform an action that
+"complements" the last key. Alternate Repeat is not limited to reverse
+repeating, and it need not be symmetric. You can use it to eliminate cases of
+same-finger bigrams in your layout, that is, pairs of letters typed by the same
+finger. The following addresses the top 5 same-finger bigrams in English on
+QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt
+Repeat</kbd>.
+
+```c
+uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
+    switch (keycode) {
+        case KC_E: return KC_D;  // For "ED" bigram.
+        case KC_D: return KC_E;  // For "DE" bigram.
+        case KC_C: return KC_E;  // For "CE" bigram.
+        case KC_L: return KC_O;  // For "LO" bigram.
+        case KC_U: return KC_N;  // For "UN" bigram.
+    }
+
+    return KC_TRNS;
+}
+```
+
+#### Typing shortcuts
+
+A useful possibility is having Alternate Repeat press [a
+macro](feature_macros.md). This way macros can be used without having to
+dedicate keys to them. The following defines a couple shortcuts.
+
+* Typing <kbd>K</kbd>, <kbd>Alt Repeat</kbd> produces "`keyboard`," with the
+  initial "`k`" typed as usual and the "`eybord`" produced by the macro. 
+* Typing <kbd>.</kbd>, <kbd>Alt Repeat</kbd> produces "`../`," handy for "up
+  directory" on the shell. Similary, <kbd>.</kbd> types the initial "`.`" and 
+  "`./`" is produced by the macro.
+
+```c
+enum custom_keycodes {
+    M_KEYBOARD = SAFE_RANGE,
+    M_UPDIR,
+    // Other custom keys...
+};
+
+uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
+    switch (keycode) {
+        case KC_K: return M_KEYBOARD;
+        case KC_DOT: return M_UPDIR;
+    }
+
+    return KC_TRNS;
+}
+
+bool process_record_user(uint16_t keycode, keyrecord_t* record) {
+    switch (keycode) {
+        case M_KEYBOARD: SEND_STRING(/*k*/"eyboard"); break;
+        case M_UPDIR: SEND_STRING(/*.*/"./"); break;
+    }
+    return true;
+}
+```
+
+## Ignoring certain keys and mods
+
+In tracking what is "the last key" to be repeated or alternate repeated,
+modifier and layer switch keys are always ignored. This makes it possible to set
+some mods and change layers between pressing a key and repeating it. By default,
+all other (non-modifier, non-layer switch) keys are remembered so that they are
+eligible for repeating. To configure additional keys to be ignored, define
+`remember_last_key_user()` in your keymap.c.
+
+#### Ignoring a key
+
+The following ignores the Backspace key:
+
+```c
+bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
+                            uint8_t* remembered_mods) {
+    switch (keycode) {
+        case KC_BSPC:
+            return false;  // Ignore backspace.
+    }
+
+    return true;  // Other keys can be repeated.
+}
+```
+
+Then for instance, the Repeat key in <kbd>Left Arrow</kbd>,
+<kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of
+repeating Backspace.
+
+The `remember_last_key_user()` callback is called on every key press excluding
+modifiers and layer switches. Returning true indicates the key is remembered,
+while false means it is ignored.
+
+#### Filtering remembered mods
+
+The `remembered_mods` arg represents the mods that will be remembered with
+this key. It can be modified to forget certain mods. This may be
+useful to forget capitalization when repeating shifted letters, so that "Aaron"
+does not becom "AAron":
+
+```c
+bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
+                            uint8_t* remembered_mods) {
+    // Forget Shift on letter keys when Shift or AltGr are the only mods.
+    switch (keycode) {
+        case KC_A ... KC_Z:
+            if ((*remembered_mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) {
+                *remembered_mods &= ~MOD_MASK_SHIFT;
+            }
+            break;
+    }
+
+    return true;
+}
+```
+
+#### Further conditions
+
+Besides checking the keycode, this callback could also make conditions based on
+the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For
+example, the following ignores keys on layer 2 as well as key combinations
+involving GUI:
+
+```c
+bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
+                            uint8_t* remembered_mods) {
+    if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) {
+        return false;  // Ignore layer 2 keys and GUI chords.
+    }
+
+    return true;  // Other keys can be repeated.
+}
+```
+
+?> See [Layer Functions](feature_layers.md#functions) and [Checking Modifier
+State](feature_advanced_keycodes.md#checking-modifier-state) for further
+details.
+ 
+
+## Handle how a key is repeated
+
+By default, pressing the Repeat Key will simply behave as if the last key
+were pressed again. This also works with macro keys with custom handlers,
+invoking the macro again. In case fine-tuning is needed for sensible repetition,
+you can handle how a key is repeated with `get_repeat_key_count()` within
+`process_record_user()`. 
+
+The `get_repeat_key_count()` function returns a signed count of times the key
+has been repeated or alternate repeated. When a key is pressed as usual,
+`get_repeat_key_count()` is 0. On the first repeat, it is 1, then the second
+repeat, 2, and so on. Negative counts are used similarly for alternate
+repeating. For instance supposing `MY_MACRO` is a custom keycode used in the
+layout:
+
+```c
+bool process_record_user(uint16_t keycode, keyrecord_t* record) {
+    switch (keycode) {
+        case MY_MACRO:
+            if (get_repeat_key_count() > 0) {
+                // MY_MACRO is being repeated!
+                if (record->event.pressed) {
+                    SEND_STRING("repeat!");    
+                }
+            } else {                          
+                // MY_MACRO is being used normally.
+                if (record->event.pressed) {  
+                    SEND_STRING("macro");
+                }
+            }
+            return false;
+     
+        // Other macros...
+    }
+    return true;
+}
+```
+
+## Handle how a key is alternate repeated
+
+Pressing the Alternate Repeat Key behaves as if the "alternate" of the last
+pressed key were pressed, if an alternate is defined. To define how a particular
+key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback
+as described above to define which keycode to use as its alternate. Beyond this,
+`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior
+when alternate repeating.
+
+The following example defines `MY_MACRO` as its own alternate, and specially
+handles repeating and alternate repeating:
+
+```c
+uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
+    switch (keycode) {
+        case MY_MACRO: return MY_MACRO;  // MY_MACRO is its own alternate.
+    }
+    return KC_TRNS;
+}
+
+bool process_record_user(uint16_t keycode, keyrecord_t* record) {
+    switch (keycode) {
+        case MY_MACRO:
+            if (get_repeat_key_count() > 0) {        // Repeating.
+                if (record->event.pressed) {
+                    SEND_STRING("repeat!");    
+                }
+            } else if (get_repeat_key_count() < 0) { // Alternate repeating.
+                if (record->event.pressed) {
+                    SEND_STRING("alt repeat!");
+                }
+            } else {                                 // Used normally.
+                if (record->event.pressed) {  
+                    SEND_STRING("macro");
+                }
+            }
+            return false;
+     
+        // Other macros...
+    }
+    return true;
+}
+```
+
+
+## Functions
+
+| Function                       | Description                                                            |
+|--------------------------------|------------------------------------------------------------------------|
+| `get_last_keycode()`           | The last key's keycode, the key to be repeated.                        |
+| `get_last_mods()`              | Mods to apply when repeating.                                          |
+| `set_last_keycode(kc)`         | Set the keycode to be repeated.                                        |
+| `set_last_mods(mods)`          | Set the mods to apply when repeating.                                  |
+| `get_repeat_key_count()`       | Signed count of times the key has been repeated or alternate repeated. |
+| `get_alt_repeat_key_keycode()` | Keycode to be used for alternate repeating.                            |
+ 
+
+## Additional "Alternate" keys
+
+By leveraging `get_last_keycode()` in macros, it is possible to define
+additional, distinct "Alternate Repeat"-like keys. The following defines two
+keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common
+English 5-gram letter patterns, taking inspiration from
+[Stenotype](feature_stenography.md):
+
+
+| Typing                           | Produces | Typing                           | Produces |
+|----------------------------------|----------|----------------------------------|----------|
+| <kbd>A</kbd>, <kbd>ALTREP2</kbd> | `ation`  | <kbd>A</kbd>, <kbd>ALTREP3</kbd> | `about`   |
+| <kbd>I</kbd>, <kbd>ALTREP2</kbd> | `ition`  | <kbd>I</kbd>, <kbd>ALTREP3</kbd> | `inter`   |
+| <kbd>S</kbd>, <kbd>ALTREP2</kbd> | `ssion`  | <kbd>S</kbd>, <kbd>ALTREP3</kbd> | `state`   |
+| <kbd>T</kbd>, <kbd>ALTREP2</kbd> | `their`  | <kbd>T</kbd>, <kbd>ALTREP3</kbd> | `there`   |
+| <kbd>W</kbd>, <kbd>ALTREP2</kbd> | `which`  | <kbd>W</kbd>, <kbd>ALTREP3</kbd> | `would`   |
+
+```c
+enum custom_keycodes {
+    ALTREP2 = SAFE_RANGE,
+    ALTREP3,
+};
+
+// Use ALTREP2 and ALTREP3 in your layout...
+
+bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
+                            uint8_t* remembered_mods) {
+    switch (keycode) {
+        case ALTREP2:
+        case ALTREP3:
+            return false;  // Ignore ALTREP keys.
+    }
+
+    return true;  // Other keys can be repeated.
+}
+
+static void process_altrep2(uint16_t keycode, uint8_t mods) {
+    switch (keycode) {
+        case KC_A: SEND_STRING(/*a*/"tion"); break;
+        case KC_I: SEND_STRING(/*i*/"tion"); break;
+        case KC_S: SEND_STRING(/*s*/"sion"); break;
+        case KC_T: SEND_STRING(/*t*/"heir"); break;
+        case KC_W: SEND_STRING(/*w*/"hich"); break;
+    }
+}
+
+static void process_altrep3(uint16_t keycode, uint8_t mods) {
+    switch (keycode) {
+        case KC_A: SEND_STRING(/*a*/"bout"); break;
+        case KC_I: SEND_STRING(/*i*/"nter"); break;
+        case KC_S: SEND_STRING(/*s*/"tate"); break;
+        case KC_T: SEND_STRING(/*t*/"here"); break;
+        case KC_W: SEND_STRING(/*w*/"ould"); break;
+    }
+}
+
+bool process_record_user(uint16_t keycode, keyrecord_t* record) {
+    switch (keycode) {
+        case ALTREP2: 
+            if (record->event.pressed) {
+                process_altrep2(get_last_keycode(), get_last_mods());
+            }
+            return false;
+
+        case ALTREP3:
+            if (record->event.pressed) {
+                process_altrep3(get_last_keycode(), get_last_mods());
+            }
+            return false;
+    }
+
+    return true;
+}
+```
+
diff --git a/docs/ja/_summary.md b/docs/ja/_summary.md
index e49853bfd4..4d6f2348d5 100644
--- a/docs/ja/_summary.md
+++ b/docs/ja/_summary.md
@@ -68,6 +68,7 @@
     * [モッドタップ](ja/mod_tap.md)
     * [マクロ](ja/feature_macros.md)
     * [マウスキー](ja/feature_mouse_keys.md)
+    * [Repeat Key](ja/feature_repeat_key.md)
     * [Space Cadet Shift](ja/feature_space_cadet.md)
     * [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md)
 
diff --git a/docs/keycodes.md b/docs/keycodes.md
index cad050ccf7..e5b6246af7 100644
--- a/docs/keycodes.md
+++ b/docs/keycodes.md
@@ -803,6 +803,15 @@ See also: [Programmable Button](feature_programmable_button.md)
 |`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31|
 |`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32|
 
+## Repeat Key :id=repeat-key
+
+See also: [Repeat Key](feature_repeat_key.md)
+
+|Keycode                |Aliases  |Description                          |
+|-----------------------|---------|-------------------------------------|
+|`QK_REPEAT_KEY`        |`QK_REP` |Repeat the last pressed key          |
+|`QK_ALT_REPEAT_KEY`    |`QK_AREP`|Perform alternate of the last key    |
+
 ## Space Cadet :id=space-cadet
 
 See also: [Space Cadet](feature_space_cadet.md)
diff --git a/docs/zh-cn/_summary.md b/docs/zh-cn/_summary.md
index b8c26ac275..0fc92e33d3 100644
--- a/docs/zh-cn/_summary.md
+++ b/docs/zh-cn/_summary.md
@@ -73,6 +73,7 @@
     * [Mod-Tap](zh-cn/mod_tap.md)
     * [宏](zh-cn/feature_macros.md)
     * [鼠标键](zh-cn/feature_mouse_keys.md)
+    * [Repeat Key](zh-cn/feature_repeat_key.md)
     * [Space Cadet Shift](zh-cn/feature_space_cadet.md)
     * [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md)