This commit is contained in:
parent
4dc5811f00
commit
ee0a3c3be0
61 changed files with 5743 additions and 0 deletions
68
modules/getreuer/select_word/README.md
Normal file
68
modules/getreuer/select_word/README.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Select Word
|
||||
|
||||
<table>
|
||||
<tr><td><b>Module</b></td><td><tt>getreuer/select_word</tt></td></tr>
|
||||
<tr><td><b>Version</b></td><td>2025-03-07</td></tr>
|
||||
<tr><td><b>Maintainer</b></td><td>Pascal Getreuer (@getreuer)</td></tr>
|
||||
<tr><td><b>License</b></td><td><a href="../LICENSE.txt">Apache 2.0</a></td></tr>
|
||||
<tr><td><b>Documentation</b></td><td>
|
||||
<a href="https://getreuer.info/posts/keyboards/select-word">https://getreuer.info/posts/keyboards/select-word</a>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
This is a community module adaptation of [Select
|
||||
Word](https://getreuer.info/posts/keyboards/select-word) for selecting words and
|
||||
lines, assuming conventional text editing hotkeys.
|
||||
|
||||
## Use
|
||||
|
||||
Add the following to your `keymap.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"modules": ["getreuer/select_word"]
|
||||
}
|
||||
```
|
||||
|
||||
Then use one or more of the following keycodes in your layout:
|
||||
|
||||
| Keycode | Short alias | Description |
|
||||
|--------------------|-------------|--------------------------------------------------------|
|
||||
| `SELECT_WORD` | `SELWORD` | Forward word selection. Or with Shift, line selection. |
|
||||
| `SELECT_WORD_BACK` | `SELWBAK` | Backward word selection. |
|
||||
| `SELECT_WORD_LINE` | `SELLINE` | Line selection. |
|
||||
|
||||
Press `SELWORD` to select the current word. Press it again to extend the
|
||||
selection to the following word. The effect is similar to word selection (`W`)
|
||||
in the [Kakoune editor](https://kakoune.org). Or for line selection, press
|
||||
`SELWORD` with shift to select the current line, and press it again to extend
|
||||
the selection to the following line.
|
||||
|
||||
## Mac hotkeys
|
||||
|
||||
Different hotkeys are needed to perform word and line selection on Mac OS. There
|
||||
are several ways that Select Word can be configured to send the appropriate
|
||||
hotkeys:
|
||||
|
||||
* To hardcode to Mac hotkeys, define in your `config.h` file:
|
||||
|
||||
~~~{.c}
|
||||
#define SELECT_WORD_OS_MAC
|
||||
~~~
|
||||
|
||||
* If [OS Detection](https://docs.qmk.fm/features/os_detection) is enabled,
|
||||
Select Word uses it determine which kind of hotkeys to send.
|
||||
|
||||
* For direct control, define in `config.h`:
|
||||
|
||||
~~~{.c}
|
||||
#define SELECT_WORD_OS_DYNAMIC
|
||||
~~~
|
||||
|
||||
Then in `keymap.c`, define the callback `select_word_host_is_mac()`. Return
|
||||
true for Mac hotkeys, false for Windows/Linux.
|
||||
|
||||
See the [Select Word
|
||||
documentation](https://getreuer.info/posts/keyboards/select-word) for further
|
||||
configuration options and details.
|
||||
|
17
modules/getreuer/select_word/introspection.h
Normal file
17
modules/getreuer/select_word/introspection.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "select_word.h"
|
||||
|
24
modules/getreuer/select_word/qmk_module.json
Normal file
24
modules/getreuer/select_word/qmk_module.json
Normal file
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"module_name": "Select Word",
|
||||
"maintainer": "getreuer",
|
||||
"keycodes": [
|
||||
{
|
||||
"key": "SELECT_WORD",
|
||||
"aliases": [
|
||||
"SELWORD"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SELECT_WORD_BACK",
|
||||
"aliases": [
|
||||
"SELWBAK"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "SELECT_LINE",
|
||||
"aliases": [
|
||||
"SELLINE"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
301
modules/getreuer/select_word/select_word.c
Normal file
301
modules/getreuer/select_word/select_word.c
Normal file
|
@ -0,0 +1,301 @@
|
|||
// Copyright 2021-2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @file select_word.c
|
||||
* @brief Select Word community module implementation
|
||||
*
|
||||
* For full documentation, see
|
||||
* <https://getreuer.info/posts/keyboards/select-word>
|
||||
*/
|
||||
|
||||
#include "select_word.h"
|
||||
|
||||
ASSERT_COMMUNITY_MODULES_MIN_API_VERSION(1, 0, 0);
|
||||
|
||||
// Default to a timeout of 5 seconds.
|
||||
#ifndef SELECT_WORD_TIMEOUT
|
||||
# define SELECT_WORD_TIMEOUT 5000
|
||||
#endif // SELECT_WORD_TIMEOUT
|
||||
|
||||
static int8_t selection_dir = 0;
|
||||
static bool reset_before_next_event = false;
|
||||
static uint8_t registered_hotkey = KC_NO;
|
||||
|
||||
// Macro `IS_MAC` determines whether to use Mac vs. Windows/Linux hotkeys:
|
||||
//
|
||||
// * OS Detection is used if it is enabled.
|
||||
//
|
||||
// * With SELECT_WORD_OS_DYNAMIC, the user may define callback
|
||||
// select_word_host_is_mac().
|
||||
//
|
||||
// * Otherwise, the assumed OS is set at compile time, to Window/Linux by
|
||||
// default, or to Mac with SELECT_WORD_OS_MAC.
|
||||
#if defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE)
|
||||
__attribute__((weak)) bool select_word_host_is_mac(void) {
|
||||
# ifdef OS_DETECTION_ENABLE // Use OS Detection if enabled.
|
||||
switch (detected_host_os()) {
|
||||
case OS_LINUX:
|
||||
case OS_WINDOWS:
|
||||
return false;
|
||||
case OS_MACOS:
|
||||
case OS_IOS:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
# endif // OS_DETECTION_ENABLE
|
||||
# ifdef SELECT_WORD_OS_MAC
|
||||
return true;
|
||||
# else
|
||||
return false;
|
||||
# endif // SELECT_WORD_OS_MAC
|
||||
}
|
||||
# define IS_MAC select_word_host_is_mac()
|
||||
#else
|
||||
# ifdef SELECT_WORD_OS_MAC
|
||||
# define IS_MAC true
|
||||
# else
|
||||
# define IS_MAC false
|
||||
# endif // SELECT_WORD_OS_MAC
|
||||
#endif // defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE)
|
||||
|
||||
// Idle timeout timer to reset Select Word after a period of inactivity.
|
||||
#if SELECT_WORD_TIMEOUT > 0
|
||||
# if SELECT_WORD_TIMEOUT < 100 || SELECT_WORD_TIMEOUT > 30000
|
||||
// Constrain timeout to a sensible range. With the 16-bit timer, the longest
|
||||
// representable timeout is 32768 ms, rounded here to 30000 ms = half a minute.
|
||||
# error "select_word: SELECT_WORD_TIMEOUT must be between 100 and 30000 ms"
|
||||
# endif
|
||||
|
||||
static uint16_t idle_timer = 0;
|
||||
|
||||
static void restart_idle_timer(void) {
|
||||
idle_timer = (timer_read() + SELECT_WORD_TIMEOUT) | 1;
|
||||
}
|
||||
|
||||
void housekeeping_task_select_word(void) {
|
||||
if (idle_timer && timer_expired(timer_read(), idle_timer)) {
|
||||
idle_timer = 0;
|
||||
selection_dir = 0;
|
||||
}
|
||||
}
|
||||
#endif // SELECT_WORD_TIMEOUT > 0
|
||||
|
||||
static void clear_all_mods(void) {
|
||||
clear_mods();
|
||||
clear_weak_mods();
|
||||
#ifndef NO_ACTION_ONESHOT
|
||||
clear_oneshot_mods();
|
||||
#endif // NO_ACTION_ONESHOT
|
||||
}
|
||||
|
||||
static void select_word_in_dir(int8_t dir) {
|
||||
// With Windows and Linux (non-Mac) systems:
|
||||
// dir < 0: Backward word selection: Ctrl+Left, Ctrl+Right, Ctrl+Shift+Left.
|
||||
// dir > 0: Forward word selection: Ctrl+Right, Ctrl+Left, Ctrl+Shift+Right.
|
||||
// Or to extend an existing selection:
|
||||
// dir < 0: Backward word selection: Ctrl+Shift+Left.
|
||||
// dir > 0: Forward word selection: Ctrl+Shift+Right.
|
||||
//
|
||||
// With Mac OS, use Alt (Opt) instead of Ctrl:
|
||||
// dir < 0: Backward word selection: Alt+Left, Alt+Right, Alt+Shift+Left.
|
||||
// dir > 0: Forward word selection: Alt+Right, Alt+Left, Alt+Shift+Right.
|
||||
// Or to extend an existing selection:
|
||||
// dir < 0: Backward word selection: Alt+Shift+Left.
|
||||
// dir > 0: Forward word selection: Alt+Shift+Right.
|
||||
reset_before_next_event = false;
|
||||
const uint8_t saved_mods = get_mods();
|
||||
clear_all_mods();
|
||||
|
||||
if (selection_dir && (selection_dir < 0) != (dir < 0)) { // Reversal.
|
||||
send_keyboard_report();
|
||||
tap_code_delay((dir < 0) ? KC_RGHT : KC_LEFT, TAP_CODE_DELAY);
|
||||
}
|
||||
|
||||
add_mods(IS_MAC ? MOD_BIT_LALT : MOD_BIT_LCTRL);
|
||||
|
||||
if (selection_dir == 0) { // Initial selection.
|
||||
send_keyboard_report();
|
||||
send_string_with_delay_P(
|
||||
(dir < 0) ? PSTR(SS_TAP(X_LEFT) SS_TAP(X_RGHT))
|
||||
: PSTR(SS_TAP(X_RGHT) SS_TAP(X_LEFT)),
|
||||
TAP_CODE_DELAY);
|
||||
}
|
||||
|
||||
register_mods(MOD_BIT_LSHIFT);
|
||||
registered_hotkey = (dir < 0) ? KC_LEFT : KC_RGHT;
|
||||
register_code(registered_hotkey);
|
||||
|
||||
set_mods(saved_mods);
|
||||
selection_dir = dir;
|
||||
}
|
||||
|
||||
static void select_line(void) {
|
||||
// With Windows and Linux (non-Mac) systems:
|
||||
// Home, Shift+End.
|
||||
// Or to extend an existing selection:
|
||||
// Shift+Down.
|
||||
//
|
||||
// With Mac OS, use GUI (Command) + arrows:
|
||||
// GUI+Left, Shift+GUI+Right.
|
||||
// Or to extend an existing selection:
|
||||
// Shift+Down.
|
||||
reset_before_next_event = false;
|
||||
const uint8_t saved_mods = get_mods();
|
||||
clear_all_mods();
|
||||
|
||||
if (selection_dir != 2) {
|
||||
send_keyboard_report();
|
||||
send_string_with_delay_P(
|
||||
IS_MAC ? PSTR(SS_LGUI(SS_TAP(X_LEFT) SS_LSFT(SS_TAP(X_RGHT))))
|
||||
: PSTR(SS_TAP(X_HOME) SS_LSFT(SS_TAP(X_END))),
|
||||
TAP_CODE_DELAY);
|
||||
} else {
|
||||
register_mods(MOD_BIT_LSHIFT);
|
||||
registered_hotkey = KC_DOWN;
|
||||
register_code(KC_DOWN);
|
||||
}
|
||||
|
||||
set_mods(saved_mods);
|
||||
selection_dir = 2;
|
||||
}
|
||||
|
||||
void select_word_register(char action) {
|
||||
if (registered_hotkey) {
|
||||
select_word_unregister();
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 'W':
|
||||
select_word_in_dir(1);
|
||||
break;
|
||||
case 'B':
|
||||
select_word_in_dir(-1);
|
||||
break;
|
||||
case 'L':
|
||||
select_line();
|
||||
break;
|
||||
}
|
||||
|
||||
#if SELECT_WORD_TIMEOUT > 0
|
||||
idle_timer = 0;
|
||||
#endif // SELECT_WORD_TIMEOUT > 0
|
||||
}
|
||||
|
||||
void select_word_unregister(void) {
|
||||
reset_before_next_event = false;
|
||||
unregister_code(registered_hotkey);
|
||||
|
||||
if (registered_hotkey == KC_DOWN) {
|
||||
// When using line selection to select multiple lines, tap Shift+End (or on
|
||||
// Mac, GUI+Shift+Right) on release to ensure the selection extends to the
|
||||
// end of the current line.
|
||||
const uint8_t saved_mods = get_mods();
|
||||
clear_all_mods();
|
||||
send_keyboard_report();
|
||||
send_string_with_delay_P(
|
||||
IS_MAC ? PSTR(SS_LGUI(SS_LSFT(SS_TAP(X_RGHT))))
|
||||
: PSTR(SS_LSFT(SS_TAP(X_END))),
|
||||
TAP_CODE_DELAY);
|
||||
set_mods(saved_mods);
|
||||
}
|
||||
|
||||
registered_hotkey = KC_NO;
|
||||
#if SELECT_WORD_TIMEOUT > 0
|
||||
restart_idle_timer();
|
||||
#endif // SELECT_WORD_TIMEOUT > 0
|
||||
}
|
||||
|
||||
bool process_record_select_word(uint16_t keycode, keyrecord_t* record) {
|
||||
if (!process_record_select_word_kb(keycode, record)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selection_dir) {
|
||||
if (reset_before_next_event) {
|
||||
selection_dir = 0;
|
||||
}
|
||||
|
||||
// Ignore most modifier and layer switch keys.
|
||||
switch (keycode) {
|
||||
case MODIFIER_KEYCODE_RANGE:
|
||||
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
|
||||
case QK_LAYER_MOD ... QK_LAYER_MOD_MAX:
|
||||
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
|
||||
case QK_TO ... QK_TO_MAX:
|
||||
case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
|
||||
#ifndef NO_ACTION_ONESHOT
|
||||
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
|
||||
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
|
||||
#endif // NO_ACTION_ONESHOT
|
||||
#ifdef LAYER_LOCK_ENABLE
|
||||
case QK_LLCK:
|
||||
#endif // LAYER_LOCK_ENABLE
|
||||
return true;
|
||||
#ifndef NO_ACTION_TAPPING
|
||||
// Ignore hold events on mod-tap and layer-tap keys.
|
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||
if (record->tap.count == 0) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
#endif // NO_ACTION_TAPPING
|
||||
}
|
||||
|
||||
reset_before_next_event = true;
|
||||
}
|
||||
|
||||
#if SELECT_WORD_TIMEOUT > 0
|
||||
if (idle_timer) {
|
||||
restart_idle_timer();
|
||||
}
|
||||
#endif // SELECT_WORD_TIMEOUT > 0
|
||||
|
||||
const bool shifted = MOD_MASK_SHIFT & (get_mods() | get_weak_mods()
|
||||
#ifndef NO_ACTION_ONESHOT
|
||||
| get_oneshot_mods()
|
||||
#endif // NO_ACTION_ONESHOT
|
||||
);
|
||||
|
||||
switch (keycode) {
|
||||
case SELECT_WORD:
|
||||
if (record->event.pressed) {
|
||||
select_word_register(shifted ? 'L' : 'W');
|
||||
} else {
|
||||
select_word_unregister();
|
||||
}
|
||||
return false;
|
||||
|
||||
case SELECT_WORD_BACK:
|
||||
if (record->event.pressed) {
|
||||
select_word_register('B');
|
||||
} else {
|
||||
select_word_unregister();
|
||||
}
|
||||
return false;
|
||||
|
||||
case SELECT_LINE:
|
||||
if (record->event.pressed) {
|
||||
select_word_register('L');
|
||||
} else {
|
||||
select_word_unregister();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
91
modules/getreuer/select_word/select_word.h
Normal file
91
modules/getreuer/select_word/select_word.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
// Copyright 2021-2025 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* @file select_word.h
|
||||
* @brief Select Word community module: select words and lines.
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
*
|
||||
* Implements a button that selects the current word, assuming conventional text
|
||||
* editor hotkeys. Pressing it again extends the selection to the following
|
||||
* word. The effect is similar to word selection (W) in the Kakoune editor.
|
||||
*
|
||||
* Pressing the button with shift selects the current line, and pressing the
|
||||
* button again extends the selection to the following line.
|
||||
*
|
||||
* For full documentation, see
|
||||
* <https://getreuer.info/posts/keyboards/select-word>
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quantum.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Registers (presses) 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
|
||||
*
|
||||
* A selection is first registered with `select_word_register(action)`. This
|
||||
* should be followed by a call to `select_word_unregister()` to unregister 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()` must be followed by `select_word_unregister()`.
|
||||
*
|
||||
* @param action Type of selection to perform.
|
||||
*/
|
||||
void select_word_register(char action);
|
||||
|
||||
/** Unregisters (releases) selection hotkey. */
|
||||
void select_word_unregister(void);
|
||||
|
||||
/** Registers and unregisters ("taps") selection `action.` */
|
||||
static inline void select_word_tap(char action) {
|
||||
select_word_register(action);
|
||||
wait_ms(TAP_CODE_DELAY);
|
||||
select_word_unregister();
|
||||
}
|
||||
|
||||
#if defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE)
|
||||
/**
|
||||
* @brief Callback for whether the host uses Mac vs. Windows/Linux hotkeys.
|
||||
*
|
||||
* Optionally, this callback may be defined to indicate dynamically whether the
|
||||
* keyboard is being used with a Mac or non-Mac system.
|
||||
*
|
||||
* 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); // Supposing layer 1 = base layer for Mac.
|
||||
* }
|
||||
*/
|
||||
bool select_word_host_is_mac(void);
|
||||
#endif // defined(SELECT_WORD_OS_DYNAMIC) || defined(OS_DETECTION_ENABLE)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
Loading…
Add table
Add a link
Reference in a new issue