#ifdef ENABLE_HID_SPELLCHECK #include "keymap_russian.h" #include "keymap_us_international.h" #include "features/hid_spellcheck.h" #include "features/beta_rawhid.h" #include "extra_mappings.h" uint8_t spellcheck_buffer[SPELLCHECK_BUFFER_MAX] = {0}; uint8_t spellcheck_buffer_size = 0; // Return one of the following. #define SPELLCHECK_WORD 2 #define SPELLCHECK_SPACE 1 #define SPELLCHECK_NEITHER 0 uint8_t keycode_type_en(uint8_t mods, uint16_t keycode) { if ( KC_A <= keycode && keycode <= KC_Z // basic letters ) { return SPELLCHECK_WORD; } else if ( ( // Include these (KC_1 <= keycode && keycode <= KC_SLSH) || // Symbols // Treat " (shifted ') as a word boundary. ((keycode == KC_QUOT) && ((mods & MOD_MASK_SHIFT) != 0)) ) && !( // Don't include these (keycode == KC_ESC) || (keycode == KC_ENTER) ) ) { return SPELLCHECK_SPACE; } else { return SPELLCHECK_NEITHER; } } uint8_t keycode_type_ru(uint8_t mods, uint16_t keycode) { if ( KC_A <= keycode && keycode <= KC_Z // basic letters ) { return SPELLCHECK_WORD; } else if ( KC_1 <= keycode && keycode <= KC_0 ) { return SPELLCHECK_SPACE; } switch (keycode) { case RU_YO: case RU_HA: case RU_HARD: case RU_E: case RU_BE: case RU_YU: return SPELLCHECK_WORD; case RU_MINS: case RU_EQL: case RU_DOT: case RU_BSLS: return SPELLCHECK_SPACE; } return SPELLCHECK_NEITHER; } bool process_spellcheck(uint16_t keycode, keyrecord_t* record) { // Ignore key release; we only process key presses. if (!record->event.pressed) { return true; } #ifndef NO_ACTION_ONESHOT const uint8_t mods = get_mods() | get_oneshot_mods(); #else const uint8_t mods = get_mods(); #endif // Disable autocorrection while a mod other than shift is active. if ((mods & ~MOD_MASK_SHIFT) != 0) { spellcheck_buffer_size = 0; return true; } // The following switch cases address various kinds of keycodes. This logic is // split over two switches rather than merged into one. The first switch may // extract a basic keycode which is then further handled by the second switch, // e.g. a layer-tap key with Caps Lock `LT(layer, KC_CAPS)`. switch (keycode) { #ifndef NO_ACTION_TAPPING case QK_MOD_TAP ... QK_MOD_TAP_MAX: // Tap-hold keys. #ifndef NO_ACTION_LAYER case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: #endif // Ignore when tap-hold keys are held. if (record->tap.count == 0) { return true; } // Otherwise when tapped, get the basic keycode. // Fallthrough intended. #endif // Handle shifted keys, e.g. symbols like KC_EXLM = S(KC_1). case QK_LSFT ... QK_LSFT + 255: case QK_RSFT ... QK_RSFT + 255: keycode &= 0xff; // Get the basic keycode. break; // NOTE: Space Cadet keys expose no info to check whether they are being // tapped vs. held. This makes autocorrection ambiguous, e.g. KC_LCPO might // be '(', which we would treat as a word break, or it might be shift, which // we would treat as having no effect. To behave cautiously, we allow Space // Cadet keycodes to fall to the logic below and clear autocorrection state. } switch (keycode) { // Ignore shifts, Caps Lock, one-shot mods, and layer switch keys. case KC_NO: case KC_LSFT: case KC_RSFT: case KC_CAPS: case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: case QK_TO ... QK_TO_MAX: case QK_MOMENTARY ... QK_MOMENTARY_MAX: case QK_DEF_LAYER ... QK_DEF_LAYER_MAX: case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: return true; // Ignore these keys. } if (keycode == KC_BSPC) { // Remove last character from buffer if (spellcheck_buffer_size > 0) { --spellcheck_buffer_size; } return true; } uint8_t keycode_type; switch (layer_layouts[biton32(layer_state)]) { case LAYOUT_EN: keycode_type = keycode_type_en(mods, keycode); break; case LAYOUT_RU: keycode_type = keycode_type_ru(mods, keycode); break; default: keycode_type = SPELLCHECK_NEITHER; break; } switch(keycode_type) { // No modifications are needed if this is a regular word character case SPELLCHECK_WORD: // If the buffer is full, rotate it to discard the oldest character. if (spellcheck_buffer_size >= SPELLCHECK_BUFFER_MAX) { memmove(spellcheck_buffer, spellcheck_buffer + 1, SPELLCHECK_BUFFER_MAX - 1); spellcheck_buffer_size = SPELLCHECK_BUFFER_MAX - 1; } spellcheck_buffer[spellcheck_buffer_size++] = (uint8_t) keycode; return true; case SPELLCHECK_SPACE: // If this is a word break, send word and reset buffer if (spellcheck_buffer_size > 0) { hid_send_word(); spellcheck_buffer_size = 0; } return true; case SPELLCHECK_NEITHER: spellcheck_buffer_size = 0; return true; } return true; } #endif