← More about keyboards

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.

Effect of pressing the macro repeatedly.

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 {
  SELWORD = SAFE_RANGE,
  // 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 {
  SELWORD = SAFE_RANGE,
  // 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:

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:

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) {
        select_word_register('B');
      } else {
        select_word_unregister();
      }
      break;

    case SELWFWD:  // Forward word selection.
      if (record->event.pressed) {
        select_word_register('W');
      } else {
        select_word_unregister();
      }
      break;

    case SELLINE:  // Line selection.
      if(record->event.pressed) {
        select_word_register('L');
      } 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.

← More about keyboards