forked from mirrors/qmk_userspace
[Core] Add getreuer's Autocorrect feature to core (#15699)
Co-authored-by: Albert Y <76888457+filterpaper@users.noreply.github.com>
This commit is contained in:
parent
d67d388e77
commit
fb29c0ae53
17 changed files with 1213 additions and 2 deletions
85
quantum/process_keycode/autocorrect_data_default.h
Normal file
85
quantum/process_keycode/autocorrect_data_default.h
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Generated code.
|
||||
|
||||
// Autocorrection dictionary (70 entries):
|
||||
// :guage -> gauge
|
||||
// :the:the: -> the
|
||||
// :thier -> their
|
||||
// :ture -> true
|
||||
// accomodate -> accommodate
|
||||
// acommodate -> accommodate
|
||||
// aparent -> apparent
|
||||
// aparrent -> apparent
|
||||
// apparant -> apparent
|
||||
// apparrent -> apparent
|
||||
// aquire -> acquire
|
||||
// becuase -> because
|
||||
// cauhgt -> caught
|
||||
// cheif -> chief
|
||||
// choosen -> chosen
|
||||
// cieling -> ceiling
|
||||
// collegue -> colleague
|
||||
// concensus -> consensus
|
||||
// contians -> contains
|
||||
// cosnt -> const
|
||||
// dervied -> derived
|
||||
// fales -> false
|
||||
// fasle -> false
|
||||
// fitler -> filter
|
||||
// flase -> false
|
||||
// foward -> forward
|
||||
// frequecy -> frequency
|
||||
// gaurantee -> guarantee
|
||||
// guaratee -> guarantee
|
||||
// heigth -> height
|
||||
// heirarchy -> hierarchy
|
||||
// inclued -> include
|
||||
// interator -> iterator
|
||||
// intput -> input
|
||||
// invliad -> invalid
|
||||
// lenght -> length
|
||||
// liasion -> liaison
|
||||
// libary -> library
|
||||
// listner -> listener
|
||||
// looses: -> loses
|
||||
// looup -> lookup
|
||||
// manefist -> manifest
|
||||
// namesapce -> namespace
|
||||
// namespcae -> namespace
|
||||
// occassion -> occasion
|
||||
// occured -> occurred
|
||||
// ouptut -> output
|
||||
// ouput -> output
|
||||
// overide -> override
|
||||
// postion -> position
|
||||
// priviledge -> privilege
|
||||
// psuedo -> pseudo
|
||||
// recieve -> receive
|
||||
// refered -> referred
|
||||
// relevent -> relevant
|
||||
// repitition -> repetition
|
||||
// retrun -> return
|
||||
// retun -> return
|
||||
// reuslt -> result
|
||||
// reutrn -> return
|
||||
// saftey -> safety
|
||||
// seperate -> separate
|
||||
// singed -> signed
|
||||
// stirng -> string
|
||||
// strign -> string
|
||||
// swithc -> switch
|
||||
// swtich -> switch
|
||||
// thresold -> threshold
|
||||
// udpate -> update
|
||||
// widht -> width
|
||||
|
||||
#define AUTOCORRECT_MIN_LENGTH 5 // ":ture"
|
||||
#define AUTOCORRECT_MAX_LENGTH 10 // "accomodate"
|
||||
|
||||
#define DICTIONARY_SIZE 1104
|
||||
|
||||
static const uint8_t autocorrect_data[DICTIONARY_SIZE] PROGMEM = {108, 43, 0, 6, 71, 0, 7, 81, 0, 8, 199, 0, 9, 240, 1, 10, 250, 1, 11, 26, 2, 17, 53, 2, 18, 190, 2, 19, 202, 2, 21, 212, 2, 22, 20, 3, 23, 67, 3, 28, 16, 4, 0, 72, 50, 0, 22, 60, 0, 0, 11, 23, 44, 8, 11, 23, 44, 0, 132, 0, 8, 22, 18, 18, 15, 0, 132, 115, 101, 115, 0, 11, 23, 12, 26, 22, 0, 129, 99, 104, 0, 68, 94, 0, 8, 106, 0, 15, 174, 0, 21, 187, 0, 0, 12, 15, 25, 17, 12, 0, 131, 97, 108, 105, 100, 0, 74, 119, 0, 12, 129, 0, 21, 140, 0, 24, 165, 0, 0, 17, 12, 22, 0, 131, 103, 110, 101, 100, 0, 25, 21, 8, 7, 0, 131, 105, 118, 101, 100, 0, 72, 147, 0, 24, 156, 0, 0, 9, 8, 21, 0, 129, 114, 101, 100, 0, 6, 6, 18, 0, 129, 114, 101, 100, 0, 15, 6, 17, 12, 0, 129, 100, 101, 0, 18, 22, 8, 21, 11, 23, 0, 130, 104, 111,
|
||||
108, 100, 0, 4, 26, 18, 9, 0, 131, 114, 119, 97, 114, 100, 0, 68, 233, 0, 6, 246, 0, 7, 4, 1, 8, 16, 1, 10, 52, 1, 15, 81, 1, 21, 90, 1, 22, 117, 1, 23, 144, 1, 24, 215, 1, 25, 228, 1, 0, 6, 19, 22, 8, 16, 4, 17, 0, 130, 97, 99, 101, 0, 19, 4, 22, 8, 16, 4, 17, 0, 131, 112, 97, 99, 101, 0, 12, 21, 8, 25, 18, 0, 130, 114, 105, 100, 101, 0, 23, 0, 68, 25, 1, 17, 36, 1, 0, 21, 4, 24, 10, 0, 130, 110, 116, 101, 101, 0, 4, 21, 24, 4, 10, 0, 135, 117, 97, 114, 97, 110, 116, 101, 101, 0, 68, 59, 1, 7, 69, 1, 0, 24, 10, 44, 0, 131, 97, 117, 103, 101, 0, 8, 15, 12, 25, 12, 21, 19, 0, 130, 103, 101, 0, 22, 4, 9, 0, 130, 108, 115, 101, 0, 76, 97, 1, 24, 109, 1, 0, 24, 20, 4, 0, 132, 99, 113, 117, 105, 114, 101, 0, 23, 44, 0,
|
||||
130, 114, 117, 101, 0, 4, 0, 79, 126, 1, 24, 134, 1, 0, 9, 0, 131, 97, 108, 115, 101, 0, 6, 8, 5, 0, 131, 97, 117, 115, 101, 0, 4, 0, 71, 156, 1, 19, 193, 1, 21, 203, 1, 0, 18, 16, 0, 80, 166, 1, 18, 181, 1, 0, 18, 6, 4, 0, 135, 99, 111, 109, 109, 111, 100, 97, 116, 101, 0, 6, 6, 4, 0, 132, 109, 111, 100, 97, 116, 101, 0, 7, 24, 0, 132, 112, 100, 97, 116, 101, 0, 8, 19, 8, 22, 0, 132, 97, 114, 97, 116, 101, 0, 10, 8, 15, 15, 18, 6, 0, 130, 97, 103, 117, 101, 0, 8, 12, 6, 8, 21, 0, 131, 101, 105, 118, 101, 0, 12, 8, 11, 6, 0, 130, 105, 101, 102, 0, 17, 0, 76, 3, 2, 21, 16, 2, 0, 15, 8, 12, 6, 0, 133, 101, 105, 108, 105, 110, 103, 0, 12, 23, 22, 0, 131, 114, 105, 110, 103, 0, 70, 33, 2, 23, 44, 2, 0, 12, 23, 26, 22, 0, 131, 105,
|
||||
116, 99, 104, 0, 10, 12, 8, 11, 0, 129, 104, 116, 0, 72, 69, 2, 10, 80, 2, 18, 89, 2, 21, 156, 2, 24, 167, 2, 0, 22, 18, 18, 11, 6, 0, 131, 115, 101, 110, 0, 12, 21, 23, 22, 0, 129, 110, 103, 0, 12, 0, 86, 98, 2, 23, 124, 2, 0, 68, 105, 2, 22, 114, 2, 0, 12, 15, 0, 131, 105, 115, 111, 110, 0, 4, 6, 6, 18, 0, 131, 105, 111, 110, 0, 76, 131, 2, 22, 146, 2, 0, 23, 12, 19, 8, 21, 0, 134, 101, 116, 105, 116, 105, 111, 110, 0, 18, 19, 0, 131, 105, 116, 105, 111, 110, 0, 23, 24, 8, 21, 0, 131, 116, 117, 114, 110, 0, 85, 174, 2, 23, 183, 2, 0, 23, 8, 21, 0, 130, 117, 114, 110, 0, 8, 21, 0, 128, 114, 110, 0, 7, 8, 24, 22, 19, 0, 131, 101, 117, 100, 111, 0, 24, 18, 18, 15, 0, 129, 107, 117, 112, 0, 72, 219, 2, 18, 3, 3, 0, 76, 229, 2, 15, 238,
|
||||
2, 17, 248, 2, 0, 11, 23, 44, 0, 130, 101, 105, 114, 0, 23, 12, 9, 0, 131, 108, 116, 101, 114, 0, 23, 22, 12, 15, 0, 130, 101, 110, 101, 114, 0, 23, 4, 21, 8, 23, 17, 12, 0, 135, 116, 101, 114, 97, 116, 111, 114, 0, 72, 30, 3, 17, 38, 3, 24, 51, 3, 0, 15, 4, 9, 0, 129, 115, 101, 0, 4, 12, 23, 17, 18, 6, 0, 131, 97, 105, 110, 115, 0, 22, 17, 8, 6, 17, 18, 6, 0, 133, 115, 101, 110, 115, 117, 115, 0, 74, 86, 3, 11, 96, 3, 15, 118, 3, 17, 129, 3, 22, 218, 3, 24, 232, 3, 0, 11, 24, 4, 6, 0, 130, 103, 104, 116, 0, 71, 103, 3, 10, 110, 3, 0, 12, 26, 0, 129, 116, 104, 0, 17, 8, 15, 0, 129, 116, 104, 0, 22, 24, 8, 21, 0, 131, 115, 117, 108, 116, 0, 68, 139, 3, 8, 150, 3, 22, 210, 3, 0, 21, 4, 19, 19, 4, 0, 130, 101, 110, 116, 0, 85, 157,
|
||||
3, 25, 200, 3, 0, 68, 164, 3, 21, 175, 3, 0, 19, 4, 0, 132, 112, 97, 114, 101, 110, 116, 0, 4, 19, 0, 68, 185, 3, 19, 193, 3, 0, 133, 112, 97, 114, 101, 110, 116, 0, 4, 0, 131, 101, 110, 116, 0, 8, 15, 8, 21, 0, 130, 97, 110, 116, 0, 18, 6, 0, 130, 110, 115, 116, 0, 12, 9, 8, 17, 4, 16, 0, 132, 105, 102, 101, 115, 116, 0, 83, 239, 3, 23, 6, 4, 0, 87, 246, 3, 24, 254, 3, 0, 17, 12, 0, 131, 112, 117, 116, 0, 18, 0, 130, 116, 112, 117, 116, 0, 19, 24, 18, 0, 131, 116, 112, 117, 116, 0, 70, 29, 4, 8, 41, 4, 11, 51, 4, 21, 69, 4, 0, 8, 24, 20, 8, 21, 9, 0, 129, 110, 99, 121, 0, 23, 9, 4, 22, 0, 130, 101, 116, 121, 0, 6, 21, 4, 21, 12, 8, 11, 0, 135, 105, 101, 114, 97, 114, 99, 104, 121, 0, 4, 5, 12, 15, 0, 130, 114, 97, 114, 121, 0};
|
287
quantum/process_keycode/process_autocorrect.c
Normal file
287
quantum/process_keycode/process_autocorrect.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
// Copyright 2021 Google LLC
|
||||
// Copyright 2021 @filterpaper
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Original source: https://getreuer.info/posts/keyboards/autocorrection
|
||||
|
||||
#include "process_autocorrect.h"
|
||||
#include <string.h>
|
||||
#include "keycode_config.h"
|
||||
|
||||
#if __has_include("autocorrect_data.h")
|
||||
# include "autocorrect_data.h"
|
||||
#else
|
||||
# pragma message "Autocorrect is using the default library."
|
||||
# include "autocorrect_data_default.h"
|
||||
#endif
|
||||
|
||||
static uint8_t typo_buffer[AUTOCORRECT_MAX_LENGTH] = {KC_SPC};
|
||||
static uint8_t typo_buffer_size = 1;
|
||||
|
||||
/**
|
||||
* @brief function for querying the enabled state of autocorrect
|
||||
*
|
||||
* @return true if enabled
|
||||
* @return false if disabled
|
||||
*/
|
||||
bool autocorrect_is_enabled(void) {
|
||||
return keymap_config.autocorrect_enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enables autocorrect and saves state to eeprom
|
||||
*
|
||||
*/
|
||||
void autocorrect_enable(void) {
|
||||
keymap_config.autocorrect_enable = true;
|
||||
eeconfig_update_keymap(keymap_config.raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disables autocorrect and saves state to eeprom
|
||||
*
|
||||
*/
|
||||
void autocorrect_disable(void) {
|
||||
keymap_config.autocorrect_enable = false;
|
||||
typo_buffer_size = 0;
|
||||
eeconfig_update_keymap(keymap_config.raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Toggles autocorrect's status and save state to eeprom
|
||||
*
|
||||
*/
|
||||
void autocorrect_toggle(void) {
|
||||
keymap_config.autocorrect_enable = !keymap_config.autocorrect_enable;
|
||||
typo_buffer_size = 0;
|
||||
eeconfig_update_keymap(keymap_config.raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief handler for determining if autocorrect should process keypress
|
||||
*
|
||||
* @param keycode Keycode registered by matrix press, per keymap
|
||||
* @param record keyrecord_t structure
|
||||
* @param typo_buffer_size passed along to allow resetting of autocorrect buffer
|
||||
* @param mods allow processing of mod status
|
||||
* @return true Allow autocorection
|
||||
* @return false Stop processing and escape from autocorrect.
|
||||
*/
|
||||
__attribute__((weak)) bool process_autocorrect_user(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods) {
|
||||
// See quantum_keycodes.h for reference on these matched ranges.
|
||||
switch (*keycode) {
|
||||
// Exclude these keycodes from processing.
|
||||
case KC_LSFT:
|
||||
case KC_RSFT:
|
||||
case KC_CAPS:
|
||||
case QK_TO ... QK_ONE_SHOT_LAYER_MAX:
|
||||
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_MOD_MAX:
|
||||
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
|
||||
return false;
|
||||
|
||||
// Mask for base keycode from shifted keys.
|
||||
case QK_LSFT ... QK_LSFT + 255:
|
||||
case QK_RSFT ... QK_RSFT + 255:
|
||||
if (*keycode >= QK_LSFT && *keycode <= (QK_LSFT + 255)) {
|
||||
*mods |= MOD_LSFT;
|
||||
} else {
|
||||
*mods |= MOD_RSFT;
|
||||
}
|
||||
*keycode &= 0xFF; // Get the basic keycode.
|
||||
return true;
|
||||
#ifndef NO_ACTION_TAPPING
|
||||
// Exclude tap-hold keys when they are held down
|
||||
// and mask for base keycode when they are tapped.
|
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||
# ifdef NO_ACTION_LAYER
|
||||
// Exclude Layer Tap, if layers are disabled
|
||||
// but action tapping is still enabled.
|
||||
return false;
|
||||
# endif
|
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||
// Exclude hold keycode
|
||||
if (!record->tap.count) {
|
||||
return false;
|
||||
}
|
||||
*keycode &= 0xFF;
|
||||
break;
|
||||
#else
|
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||
// Exclude if disabled
|
||||
return false;
|
||||
#endif
|
||||
// Exclude swap hands keys when they are held down
|
||||
// and mask for base keycode when they are tapped.
|
||||
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
|
||||
#ifdef SWAP_HANDS_ENABLE
|
||||
if (*keycode >= 0x56F0 || !record->tap.count) {
|
||||
return false;
|
||||
}
|
||||
*keycode &= 0xFF;
|
||||
break;
|
||||
#else
|
||||
// Exclude if disabled
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Disable autocorrect while a mod other than shift is active.
|
||||
if ((*mods & ~MOD_MASK_SHIFT) != 0) {
|
||||
*typo_buffer_size = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief handling for when autocorrection has been triggered
|
||||
*
|
||||
* @param backspaces number of characters to remove
|
||||
* @param str pointer to PROGMEM string to replace mistyped seletion with
|
||||
* @return true apply correction
|
||||
* @return false user handled replacement
|
||||
*/
|
||||
__attribute__((weak)) bool apply_autocorrect(uint8_t backspaces, const char *str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process handler for autocorrect feature
|
||||
*
|
||||
* @param keycode Keycode registered by matrix press, per keymap
|
||||
* @param record keyrecord_t structure
|
||||
* @return true Continue processing keycodes, and send to host
|
||||
* @return false Stop processing keycodes, and don't send to host
|
||||
*/
|
||||
bool process_autocorrect(uint16_t keycode, keyrecord_t *record) {
|
||||
uint8_t mods = get_mods();
|
||||
#ifndef NO_ACTION_ONESHOT
|
||||
mods |= get_oneshot_mods();
|
||||
#endif
|
||||
|
||||
if ((keycode >= AUTOCORRECT_ON && keycode <= AUTOCORRECT_TOGGLE) && record->event.pressed) {
|
||||
if (keycode == AUTOCORRECT_ON) {
|
||||
autocorrect_enable();
|
||||
} else if (keycode == AUTOCORRECT_OFF) {
|
||||
autocorrect_disable();
|
||||
} else if (keycode == AUTOCORRECT_TOGGLE) {
|
||||
autocorrect_toggle();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keymap_config.autocorrect_enable) {
|
||||
typo_buffer_size = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!record->event.pressed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// autocorrect keycode verification and extraction
|
||||
if (!process_autocorrect_user(&keycode, record, &typo_buffer_size, &mods)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// keycode buffer check
|
||||
switch (keycode) {
|
||||
case KC_A ... KC_Z:
|
||||
// process normally
|
||||
break;
|
||||
case KC_1 ... KC_0:
|
||||
case KC_TAB ... KC_SEMICOLON:
|
||||
case KC_GRAVE ... KC_SLASH:
|
||||
// Set a word boundary if space, period, digit, etc. is pressed.
|
||||
keycode = KC_SPC;
|
||||
break;
|
||||
case KC_ENTER:
|
||||
// Behave more conservatively for the enter key. Reset, so that enter
|
||||
// can't be used on a word ending.
|
||||
typo_buffer_size = 0;
|
||||
keycode = KC_SPC;
|
||||
break;
|
||||
case KC_BSPC:
|
||||
// Remove last character from the buffer.
|
||||
if (typo_buffer_size > 0) {
|
||||
--typo_buffer_size;
|
||||
}
|
||||
return true;
|
||||
case KC_QUOTE:
|
||||
// Treat " (shifted ') as a word boundary.
|
||||
if ((mods & MOD_MASK_SHIFT) != 0) {
|
||||
keycode = KC_SPC;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// 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 >= AUTOCORRECT_MAX_LENGTH) {
|
||||
memmove(typo_buffer, typo_buffer + 1, AUTOCORRECT_MAX_LENGTH - 1);
|
||||
typo_buffer_size = AUTOCORRECT_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 < AUTOCORRECT_MIN_LENGTH) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for typo in buffer using a trie stored in `autocorrect_data`.
|
||||
uint16_t state = 0;
|
||||
uint8_t code = pgm_read_byte(autocorrect_data + state);
|
||||
for (int8_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(autocorrect_data + (state += 3))) {
|
||||
if (!code) return true;
|
||||
}
|
||||
// Follow link to child node.
|
||||
state = (pgm_read_byte(autocorrect_data + state + 1) | pgm_read_byte(autocorrect_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(autocorrect_data + (++state)))) {
|
||||
++state;
|
||||
}
|
||||
|
||||
// Stop if `state` becomes an invalid index. This should not normally
|
||||
// happen, it is a safeguard in case of a bug, data corruption, etc.
|
||||
if (state >= DICTIONARY_SIZE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
code = pgm_read_byte(autocorrect_data + state);
|
||||
|
||||
if (code & 128) { // A typo was found! Apply autocorrect.
|
||||
const uint8_t backspaces = (code & 63) + !record->event.pressed;
|
||||
if (apply_autocorrect(backspaces, (char const *)(autocorrect_data + state + 1))) {
|
||||
for (uint8_t i = 0; i < backspaces; ++i) {
|
||||
tap_code(KC_BSPC);
|
||||
}
|
||||
send_string_P((char const *)(autocorrect_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;
|
||||
}
|
17
quantum/process_keycode/process_autocorrect.h
Normal file
17
quantum/process_keycode/process_autocorrect.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2021 Google LLC
|
||||
// Copyright 2021 @filterpaper
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Original source: https://getreuer.info/posts/keyboards/autocorrection
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "quantum.h"
|
||||
|
||||
bool process_autocorrect(uint16_t keycode, keyrecord_t *record);
|
||||
bool process_autocorrect_user(uint16_t *keycode, keyrecord_t *record, uint8_t *typo_buffer_size, uint8_t *mods);
|
||||
bool apply_autocorrect(uint8_t backspaces, const char *str);
|
||||
|
||||
bool autocorrect_is_enabled(void);
|
||||
void autocorrect_enable(void);
|
||||
void autocorrect_disable(void);
|
||||
void autocorrect_toggle(void);
|
Loading…
Add table
Add a link
Reference in a new issue