process_autocorrect.c 13 KB

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