Word selection QMK macro
Pascal Getreuer, 2021-11-12 (updated 2025-01-19)
Overview
This post describes a QMK macro for a button that selects the current
word, assuming conventional text editing hotkeys. Press it again to
extend the selection to the following word. The effect is similar to
word selection (W
) in the Kakoune editor.
Line selection: Similarly, press the button with shift to select the current line, and press it again to extend the selection to the following line.
Clearing the selection: During a selection, press ← or → to deselect and choose which selection endpoint to jump the cursor to.
Add it to your keymap
If you are new to QMK macros, see my macro buttons post for an intro.
Step 1: In the directory containing your
keymap.c
, create a features
subdirectory and
copy select_word.h
and select_word.c
there.
Step 2: In your keymap directory, edit the
rules.mk
file to add
SRC += features/select_word.c
Step 3: In your keymap.c
file, add a
custom keycode for activating the macro and use the new keycode
somewhere in your layout. I’ll name it SELWORD
, but you can
call it anything you like. Use this keycode somewhere in your
layout.
enum custom_keycodes {
= SAFE_RANGE,
SELWORD // Other custom keys...
};
Step 4: Below the custom keycode definition, define
SELECT_WORD_KEYCODE
as follows, setting it to
SELWORD
or whichever name you choose in the previous
step:
uint16_t SELECT_WORD_KEYCODE = SELWORD;
Or alternatively, you may skip this step and use Select Word’s functions to invoke word and line selections programmatically.
Step 5: Also in keymap.c
, call
process_select_word()
in your
process_record_user()
function:
#include "features/select_word.h"
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
if (!process_select_word(keycode, record)) { return false; }
// Your macros ...
return true;
}
When completed, keymap.c
looks like this after steps
3–5:
#include "features/select_word.h"
enum custom_keycodes {
= SAFE_RANGE,
SELWORD // Other custom keys...
};
// Use SELWORD in your layout ...
uint16_t SELECT_WORD_KEYCODE = SELWORD;
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
if (!process_select_word(keycode, record)) { return false; }
// Your macros ...
return true;
}
Mac hotkeys
On Mac OS, different hotkeys are needed for word and line selection than are conventional on Windows and Linux. There are several ways that Select Word can be configured to send the appropriate hotkeys:
Windows/Linux hotkeys are assumed by default. To default to Mac hotkeys instead, define in your
config.h
file:#define SELECT_WORD_OS_MAC
If OS Detection is enabled, Select Word uses it determine which kind of hotkeys to send. An edge case is that OS Detection is sometimes fails (
OS_UNSURE
). If this happens, Select Word’s logic falls back to the previous bullet point.For direct control, define in
config.h
:#define SELECT_WORD_OS_DYNAMIC
Then in
keymap.c
, define the callbackselect_word_host_is_mac()
. Return true for Mac hotkeys, false for Windows/Linux. If OS Detection is also enabled, theselect_word_host_is_mac()
callback takes precedence.For instance, suppose layer 0 is your base layer for Windows and layer 1 is your base layer for Mac. Indicate this by adding in
keymap.c
:bool select_word_host_is_mac(void) { return IS_LAYER_ON(1); // Layer 1 on => Mac. }
Another possibility: suppose you use Magic Keys
QK_MAGIC_TOGGLE_CTL_GUI
to swap Ctrl and GUI keys when on Mac. This can be indicated to Select Word withbool select_word_host_is_mac(void) { return mod_config(MOD_LGUI) == MOD_LCTL; // GUI/Ctrl swapped => Mac. }
Functions
For more flexibility, Select Word’s word and line selection may be invoked programmatically. This way you can control what manner of input triggers these selection actions, for instance, invoking line selection from a tap dance.
Function | Description |
---|---|
select_word_register(action) |
Register (press) selection action . |
select_word_unregister() |
Unregister (release) selection hotkey. |
select_word_tap(action) |
Register and unregister selection action . |
The action
argument in these functions specifies the
type of selection:
'W'
= word selection'B'
= backward word selection, left of the cursor'L'
= line selection
Repeating or holding these actions extends the selection.
The functions follow the pattern of QMK’s
register_code()
and unregister_code()
. A
selection hotkey is first “registered” or pressed with
select_word_register(action)
. This should be followed by a
call to select_word_unregister()
to “unregister” or release
the hotkeys. The point of these separate register and unregister calls
is to enable holding the hotkey as a means to extend the selection
range.
⚠ Warning
Forgetting to unregister results in stuck keys:
select_word_register(action)
must be followed by
select_word_unregister()
.
Alternatively, function select_word_tap(action)
may be
used to register and then immediately unregister (“tap”) selection
action
. This is a simpler method of invoking word and line
selection with the caveat that it does not perform hotkey holding.
Supposing you have defined custom keycodes SELWFWD
,
SELWBAK
, and SELLINE
, handle them in
process_record_user()
as
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
if (!process_select_word(keycode, record)) { return false; }
switch (keycode) {
case SELWBAK: // Backward word selection.
if (record->event.pressed) {
('B');
select_word_register} else {
();
select_word_unregister}
break;
case SELWFWD: // Forward word selection.
if (record->event.pressed) {
('W');
select_word_register} else {
();
select_word_unregister}
break;
case SELLINE: // Line selection.
if(record->event.pressed) {
('L');
select_word_register} else {
();
select_word_unregister}
break;
// Macros ...
}
return true;
}
If you use Alternate
Repeat Key, SELWBAK
may be defined as the alternate
repeat of SELWFWD
, and vice versa, with
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case SELWBAK: return SELWFWD;
case SELWFWD: return SELWBAK;
// ...
}
return KC_TRNS;
}
Idle timeout
Optionally, Select Word may be configured to clear its internal state
if the keyboard is idle for some time. This is useful to improve
behavior when using Select Word and a mouse together. In your config.h,
define SELECT_WORD_TIMEOUT
with a time in milliseconds:
#define SELECT_WORD_TIMEOUT 2000 // When idle, clear state after 2 seconds.
and in your keymap.c, define (or add to)
housekeeping_task_user()
as
void housekeeping_task_user(void) {
();
select_word_task// Other tasks...
}
The default behavior (when SELECT_WORD_TIMEOUT
isn’t
set, or set to 0) is that Select Word never times out, and in this case
it isn’t necessary to call select_word_task()
.
Explanation
The macro checks for events involving
SELECT_WORD_KEYCODE
. For word selection, the first press of
the macro sends the keys Ctrl+→,
Ctrl+← to move the cursor to the beginning of the word,
then holds Ctrl+Shift+→ to select to the end of the
word. On subsequent presses, Ctrl+Shift+→ is pressed
again to extend the selection to the next word.
For line selection, the macro sends Home, Shift+End on the first press, then ↓ on subsequent presses.
The state
variable keeps track of whether the macro has
done the initial press and whether it is making a word vs. line
selection.
Acknowledgements
Thanks to @Regnareb and @arkanoryn on GitHub for helpful feedback and suggestions to make Select Word better.