logo

qmk_firmware

custom branch of QMK firmware git clone https://anongit.hacktivis.me/git/qmk_firmware.git

process_autocorrect.c (13177B)


  1. // Copyright 2021 Google LLC
  2. // Copyright 2021 @filterpaper
  3. // Copyright 2023 Pablo Martinez (@elpekenin) <elpekenin@elpekenin.dev>
  4. // SPDX-License-Identifier: Apache-2.0
  5. // Original source: https://getreuer.info/posts/keyboards/autocorrection
  6. #include "process_autocorrect.h"
  7. #include <string.h>
  8. #include "keycodes.h"
  9. #include "quantum_keycodes.h"
  10. #include "keycode_config.h"
  11. #include "send_string.h"
  12. #include "action_util.h"
  13. #if __has_include("autocorrect_data.h")
  14. # include "autocorrect_data.h"
  15. #else
  16. # pragma message "Autocorrect is using the default library."
  17. # include "autocorrect_data_default.h"
  18. #endif
  19. static uint8_t typo_buffer[AUTOCORRECT_MAX_LENGTH] = {KC_SPC};
  20. static uint8_t typo_buffer_size = 1;
  21. /**
  22. * @brief function for querying the enabled state of autocorrect
  23. *
  24. * @return true if enabled
  25. * @return false if disabled
  26. */
  27. bool autocorrect_is_enabled(void) {
  28. return keymap_config.autocorrect_enable;
  29. }
  30. /**
  31. * @brief Enables autocorrect and saves state to eeprom
  32. *
  33. */
  34. void autocorrect_enable(void) {
  35. keymap_config.autocorrect_enable = true;
  36. eeconfig_update_keymap(&keymap_config);
  37. }
  38. /**
  39. * @brief Disables autocorrect and saves state to eeprom
  40. *
  41. */
  42. void autocorrect_disable(void) {
  43. keymap_config.autocorrect_enable = false;
  44. typo_buffer_size = 0;
  45. eeconfig_update_keymap(&keymap_config);
  46. }
  47. /**
  48. * @brief Toggles autocorrect's status and save state to eeprom
  49. *
  50. */
  51. void autocorrect_toggle(void) {
  52. keymap_config.autocorrect_enable = !keymap_config.autocorrect_enable;
  53. typo_buffer_size = 0;
  54. eeconfig_update_keymap(&keymap_config);
  55. }
  56. /**
  57. * @brief handler for user to override whether autocorrect should process this keypress
  58. *
  59. * @param keycode Keycode registered by matrix press, per keymap
  60. * @param record keyrecord_t structure
  61. * @param typo_buffer_size passed along to allow resetting of autocorrect buffer
  62. * @param mods allow processing of mod status
  63. * @return true Allow autocorection
  64. * @return false Stop processing and escape from autocorrect.
  65. */
  66. __attribute__((weak)) bool process_autocorrect_user(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods) {
  67. return process_autocorrect_default_handler(keycode, record, typo_buffer_size, mods);
  68. }
  69. /**
  70. * @brief fallback handler for determining if autocorrect should process this keypress
  71. * can be used by user callback to get the basic keycode being "wrapped"
  72. *
  73. * NOTE: These values may have been edited by user callback before getting here
  74. *
  75. * @param keycode Keycode registered by matrix press, per keymap
  76. * @param record keyrecord_t structure
  77. * @param typo_buffer_size passed along to allow resetting of autocorrect buffer
  78. * @param mods allow processing of mod status
  79. * @return true Allow autocorection
  80. * @return false Stop processing and escape from autocorrect.
  81. */
  82. bool process_autocorrect_default_handler(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods) {
  83. // See quantum_keycodes.h for reference on these matched ranges.
  84. switch (*keycode) {
  85. // Exclude these keycodes from processing.
  86. case KC_LSFT:
  87. case KC_RSFT:
  88. case KC_CAPS:
  89. case QK_TO ... QK_TO_MAX:
  90. case QK_MOMENTARY ... QK_MOMENTARY_MAX:
  91. case QK_DEF_LAYER ... QK_DEF_LAYER_MAX:
  92. case QK_PERSISTENT_DEF_LAYER ... QK_PERSISTENT_DEF_LAYER_MAX:
  93. case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
  94. case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
  95. case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
  96. case QK_LAYER_MOD ... QK_LAYER_MOD_MAX:
  97. case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
  98. return false;
  99. // Mask for base keycode from shifted keys.
  100. case QK_LSFT ... QK_LSFT + 255:
  101. case QK_RSFT ... QK_RSFT + 255:
  102. if (*keycode >= QK_LSFT && *keycode <= (QK_LSFT + 255)) {
  103. *mods |= MOD_LSFT;
  104. } else {
  105. *mods |= MOD_RSFT;
  106. }
  107. *keycode = QK_MODS_GET_BASIC_KEYCODE(*keycode); // Get the basic keycode.
  108. return true;
  109. #ifndef NO_ACTION_TAPPING
  110. // Exclude tap-hold keys when they are held down
  111. // and mask for base keycode when they are tapped.
  112. case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
  113. # ifdef NO_ACTION_LAYER
  114. // Exclude Layer Tap, if layers are disabled
  115. // but action tapping is still enabled.
  116. return false;
  117. # else
  118. // Exclude hold keycode
  119. if (!record->tap.count) {
  120. return false;
  121. }
  122. *keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(*keycode);
  123. break;
  124. # endif
  125. case QK_MOD_TAP ... QK_MOD_TAP_MAX:
  126. // Exclude hold keycode
  127. if (!record->tap.count) {
  128. return false;
  129. }
  130. *keycode = QK_MOD_TAP_GET_TAP_KEYCODE(*keycode);
  131. break;
  132. #else
  133. case QK_MOD_TAP ... QK_MOD_TAP_MAX:
  134. case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
  135. // Exclude if disabled
  136. return false;
  137. #endif
  138. // Exclude swap hands keys when they are held down
  139. // and mask for base keycode when they are tapped.
  140. case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
  141. #ifdef SWAP_HANDS_ENABLE
  142. // Note: IS_SWAP_HANDS_KEYCODE() actually tests for the special action keycodes like SH_TOGG, SH_TT, ...,
  143. // which currently overlap the SH_T(kc) range.
  144. if (IS_SWAP_HANDS_KEYCODE(*keycode)
  145. # ifndef NO_ACTION_TAPPING
  146. || !record->tap.count
  147. # endif // NO_ACTION_TAPPING
  148. ) {
  149. return false;
  150. }
  151. *keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(*keycode);
  152. break;
  153. #else
  154. // Exclude if disabled
  155. return false;
  156. #endif
  157. }
  158. // Disable autocorrect while a mod other than shift is active.
  159. if ((*mods & ~MOD_MASK_SHIFT) != 0) {
  160. *typo_buffer_size = 0;
  161. return false;
  162. }
  163. return true;
  164. }
  165. /**
  166. * @brief handling for when autocorrection has been triggered
  167. *
  168. * @param backspaces number of characters to remove
  169. * @param str pointer to PROGMEM string to replace mistyped seletion with
  170. * @param typo the wrong string that triggered a correction
  171. * @param correct what it would become after the changes
  172. * @return true apply correction
  173. * @return false user handled replacement
  174. */
  175. __attribute__((weak)) bool apply_autocorrect(uint8_t backspaces, const char *str, char *typo, char *correct) {
  176. return true;
  177. }
  178. /**
  179. * @brief Process handler for autocorrect feature
  180. *
  181. * @param keycode Keycode registered by matrix press, per keymap
  182. * @param record keyrecord_t structure
  183. * @return true Continue processing keycodes, and send to host
  184. * @return false Stop processing keycodes, and don't send to host
  185. */
  186. bool process_autocorrect(uint16_t keycode, keyrecord_t *record) {
  187. uint8_t mods = get_mods();
  188. #ifndef NO_ACTION_ONESHOT
  189. mods |= get_oneshot_mods();
  190. #endif
  191. if ((keycode >= QK_AUTOCORRECT_ON && keycode <= QK_AUTOCORRECT_TOGGLE) && record->event.pressed) {
  192. if (keycode == QK_AUTOCORRECT_ON) {
  193. autocorrect_enable();
  194. } else if (keycode == QK_AUTOCORRECT_OFF) {
  195. autocorrect_disable();
  196. } else if (keycode == QK_AUTOCORRECT_TOGGLE) {
  197. autocorrect_toggle();
  198. } else {
  199. return true;
  200. }
  201. return false;
  202. }
  203. if (!keymap_config.autocorrect_enable) {
  204. typo_buffer_size = 0;
  205. return true;
  206. }
  207. if (!record->event.pressed) {
  208. return true;
  209. }
  210. // autocorrect keycode verification and extraction
  211. if (!process_autocorrect_user(&keycode, record, &typo_buffer_size, &mods)) {
  212. return true;
  213. }
  214. // keycode buffer check
  215. switch (keycode) {
  216. case KC_A ... KC_Z:
  217. // process normally
  218. break;
  219. case KC_1 ... KC_0:
  220. case KC_TAB ... KC_SEMICOLON:
  221. case KC_GRAVE ... KC_SLASH:
  222. // Set a word boundary if space, period, digit, etc. is pressed.
  223. keycode = KC_SPC;
  224. break;
  225. case KC_ENTER:
  226. // Behave more conservatively for the enter key. Reset, so that enter
  227. // can't be used on a word ending.
  228. typo_buffer_size = 0;
  229. keycode = KC_SPC;
  230. break;
  231. case KC_BSPC:
  232. // Remove last character from the buffer.
  233. if (typo_buffer_size > 0) {
  234. --typo_buffer_size;
  235. }
  236. return true;
  237. case KC_QUOTE:
  238. // Treat " (shifted ') as a word boundary.
  239. if ((mods & MOD_MASK_SHIFT) != 0) {
  240. keycode = KC_SPC;
  241. }
  242. break;
  243. default:
  244. // Clear state if some other non-alpha key is pressed.
  245. typo_buffer_size = 0;
  246. return true;
  247. }
  248. // Rotate oldest character if buffer is full.
  249. if (typo_buffer_size >= AUTOCORRECT_MAX_LENGTH) {
  250. memmove(typo_buffer, typo_buffer + 1, AUTOCORRECT_MAX_LENGTH - 1);
  251. typo_buffer_size = AUTOCORRECT_MAX_LENGTH - 1;
  252. }
  253. // Append `keycode` to buffer.
  254. typo_buffer[typo_buffer_size++] = keycode;
  255. // Return if buffer is smaller than the shortest word.
  256. if (typo_buffer_size < AUTOCORRECT_MIN_LENGTH) {
  257. return true;
  258. }
  259. // Check for typo in buffer using a trie stored in `autocorrect_data`.
  260. uint16_t state = 0;
  261. uint8_t code = pgm_read_byte(autocorrect_data + state);
  262. for (int8_t i = typo_buffer_size - 1; i >= 0; --i) {
  263. uint8_t const key_i = typo_buffer[i];
  264. if (code & 64) { // Check for match in node with multiple children.
  265. code &= 63;
  266. for (; code != key_i; code = pgm_read_byte(autocorrect_data + (state += 3))) {
  267. if (!code) return true;
  268. }
  269. // Follow link to child node.
  270. state = (pgm_read_byte(autocorrect_data + state + 1) | pgm_read_byte(autocorrect_data + state + 2) << 8);
  271. // Check for match in node with single child.
  272. } else if (code != key_i) {
  273. return true;
  274. } else if (!(code = pgm_read_byte(autocorrect_data + (++state)))) {
  275. ++state;
  276. }
  277. // Stop if `state` becomes an invalid index. This should not normally
  278. // happen, it is a safeguard in case of a bug, data corruption, etc.
  279. if (state >= DICTIONARY_SIZE) {
  280. return true;
  281. }
  282. code = pgm_read_byte(autocorrect_data + state);
  283. if (code & 128) { // A typo was found! Apply autocorrect.
  284. const uint8_t backspaces = (code & 63) + !record->event.pressed;
  285. const char * changes = (const char *)(autocorrect_data + state + 1);
  286. /* Gather info about the typo'd word
  287. *
  288. * Since buffer may contain several words, delimited by spaces, we
  289. * iterate from the end to find the start and length of the typo
  290. */
  291. char typo[AUTOCORRECT_MAX_LENGTH + 1] = {0}; // extra char for null terminator
  292. uint8_t typo_len = 0;
  293. uint8_t typo_start = 0;
  294. bool space_last = typo_buffer[typo_buffer_size - 1] == KC_SPC;
  295. for (uint8_t i = typo_buffer_size; i > 0; --i) {
  296. // stop counting after finding space (unless it is the last thing)
  297. if (typo_buffer[i - 1] == KC_SPC && i != typo_buffer_size) {
  298. typo_start = i;
  299. break;
  300. }
  301. ++typo_len;
  302. }
  303. // when detecting 'typo:', reduce the length of the string by one
  304. if (space_last) {
  305. --typo_len;
  306. }
  307. // convert buffer of keycodes into a string
  308. for (uint8_t i = 0; i < typo_len; ++i) {
  309. typo[i] = typo_buffer[typo_start + i] - KC_A + 'a';
  310. }
  311. /* Gather the corrected word
  312. *
  313. * A) Correction of 'typo:' -- Code takes into account
  314. * an extra backspace to delete the space (which we dont copy)
  315. * for this reason the offset is correct to "skip" the null terminator
  316. *
  317. * B) When correcting 'typo' -- Need extra offset for terminator
  318. */
  319. char correct[AUTOCORRECT_MAX_LENGTH + 10] = {0}; // let's hope this is big enough
  320. uint8_t offset = space_last ? backspaces : backspaces + 1;
  321. strcpy(correct, typo);
  322. strcpy_P(correct + typo_len - offset, changes);
  323. if (apply_autocorrect(backspaces, changes, typo, correct)) {
  324. for (uint8_t i = 0; i < backspaces; ++i) {
  325. tap_code(KC_BSPC);
  326. }
  327. send_string_P(changes);
  328. }
  329. if (keycode == KC_SPC) {
  330. typo_buffer[0] = KC_SPC;
  331. typo_buffer_size = 1;
  332. return true;
  333. } else {
  334. typo_buffer_size = 0;
  335. return false;
  336. }
  337. }
  338. }
  339. return true;
  340. }