// Copyright 2021 Google LLC // Copyright 2022 @filterpaper // SPDX-License-Identifier: Apache-2.0 // Original source: https://getreuer.info/posts/keyboards/autocorrection #include "autocorrection.h" #include #if __has_include("autocorrection_data.h") # include "autocorrection_data.h" # if AUTOCORRECTION_MIN_LENGTH < 4 # error Minimum Length is too short and may cause overflows # endif bool process_autocorrection(uint16_t keycode, keyrecord_t* record) { static uint8_t typo_buffer[AUTOCORRECTION_MAX_LENGTH] = {KC_SPC}; static uint8_t typo_buffer_size = 1; if (keycode == AUTO_CTN) { if (record->event.pressed) { typo_buffer_size = 0; userspace_config.autocorrection ^= 1; eeconfig_update_user(userspace_config.raw); } return false; } if (!userspace_config.autocorrection) { typo_buffer_size = 0; return true; } switch (keycode) { case KC_LSFT: case KC_RSFT: return true; # ifndef NO_ACTION_TAPPING case QK_MOD_TAP ... QK_MOD_TAP_MAX: if (((keycode >> 8) & 0xF) == MOD_LSFT) { return true; } # ifndef NO_ACTION_LAYER case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: # endif if (record->event.pressed || !record->tap.count) { return true; } keycode &= 0xFF; break; # endif # ifndef NO_ACTION_ONESHOT case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: if ((keycode & 0xF) == MOD_LSFT) { return true; } # endif default: if (!record->event.pressed) { return true; } } // Subtract buffer for Backspace key, reset for other non-alpha. if (!(KC_A <= keycode && keycode <= KC_Z)) { if (keycode == KC_BSPC) { // Remove last character from the buffer. if (typo_buffer_size > 0) { --typo_buffer_size; } return true; } else if (KC_1 <= keycode && keycode <= KC_SLSH && keycode != KC_ESC) { // Set a word boundary if space, period, digit, etc. is pressed. // Behave more conservatively for the enter key. Reset, so that enter // can't be used on a word ending. if (keycode == KC_ENT) { typo_buffer_size = 0; } keycode = KC_SPC; } else { // Clear state if some other non-alpha key is pressed. typo_buffer_size = 0; return true; } } // Rotate oldest character if buffer is full. if (typo_buffer_size >= AUTOCORRECTION_MAX_LENGTH) { memmove(typo_buffer, typo_buffer + 1, AUTOCORRECTION_MAX_LENGTH - 1); typo_buffer_size = AUTOCORRECTION_MAX_LENGTH - 1; } // Append `keycode` to buffer. typo_buffer[typo_buffer_size++] = keycode; // Return if buffer is smaller than the shortest word. if (typo_buffer_size < AUTOCORRECTION_MIN_LENGTH) { return true; } // Check for typo in buffer using a trie stored in `autocorrection_data`. uint16_t state = 0; uint8_t code = pgm_read_byte(autocorrection_data + state); for (uint8_t i = typo_buffer_size - 1; i >= 0; --i) { uint8_t const key_i = typo_buffer[i]; if (code & 64) { // Check for match in node with multiple children. code &= 63; for (; code != key_i; code = pgm_read_byte(autocorrection_data + (state += 3))) { if (!code) return true; } // Follow link to child node. state = (pgm_read_byte(autocorrection_data + state + 1) | pgm_read_byte(autocorrection_data + state + 2) << 8); // Check for match in node with single child. } else if (code != key_i) { return true; } else if (!(code = pgm_read_byte(autocorrection_data + (++state)))) { ++state; } code = pgm_read_byte(autocorrection_data + state); if (code & 128) { // A typo was found! Apply autocorrection. const uint8_t backspaces = code & 63; for (uint8_t i = 0; i < backspaces; ++i) { tap_code(KC_BSPC); } send_string_P((char const*)(autocorrection_data + state + 1)); if (keycode == KC_SPC) { typo_buffer[0] = KC_SPC; typo_buffer_size = 1; return true; } else { typo_buffer_size = 0; return false; } } } return true; } #else # pragma message "Warning!!! Autocorrect is not corretly setup!" bool process_autocorrection(uint16_t keycode, keyrecord_t* record) { return true; } #endif