summaryrefslogtreecommitdiffstats
path: root/users/drashna/keyrecords
diff options
context:
space:
mode:
authorDrashna Jaelre <drashna@live.com>2022-05-30 22:02:55 -0700
committerGitHub <noreply@github.com>2022-05-30 22:02:55 -0700
commitcda343acbe45826225edac75eaa63216bf76d874 (patch)
tree398a14c907baa8e6521fc9d001a4619289e1d7a3 /users/drashna/keyrecords
parentb554e4b612d24109ce714554a306043a01382cbd (diff)
[Keymap] Drashna update for post Q2 merge (#17241)
Diffstat (limited to 'users/drashna/keyrecords')
-rw-r--r--users/drashna/keyrecords/autocorrection/autocorrection.c292
-rw-r--r--users/drashna/keyrecords/autocorrection/autocorrection.h9
-rwxr-xr-xusers/drashna/keyrecords/autocorrection/make_autocorrection_data.py186
-rw-r--r--users/drashna/keyrecords/caps_word.c139
-rw-r--r--users/drashna/keyrecords/caps_word.h85
-rw-r--r--users/drashna/keyrecords/keycodes.md5
-rw-r--r--users/drashna/keyrecords/process_records.c72
-rw-r--r--users/drashna/keyrecords/process_records.h11
-rw-r--r--users/drashna/keyrecords/secrets.md17
-rw-r--r--users/drashna/keyrecords/unicode.c21
-rw-r--r--users/drashna/keyrecords/unicode.h16
11 files changed, 384 insertions, 469 deletions
diff --git a/users/drashna/keyrecords/autocorrection/autocorrection.c b/users/drashna/keyrecords/autocorrection/autocorrection.c
index c7e938a341..682d6fd49c 100644
--- a/users/drashna/keyrecords/autocorrection/autocorrection.c
+++ b/users/drashna/keyrecords/autocorrection/autocorrection.c
@@ -10,6 +10,13 @@
# pragma GCC push_options
# pragma GCC optimize("O0")
# include "autocorrection_data.h"
+# ifndef AUTOCORRECTION_MIN_LENGTH
+# define AUTOCORRECTION_MIN_LENGTH AUTOCORRECT_MIN_LENGTH
+# endif
+# ifndef AUTOCORRECTION_MAX_LENGTH
+# define AUTOCORRECTION_MAX_LENGTH AUTOCORRECT_MAX_LENGTH
+# endif
+# define autocorrection_data autocorrect_data
# if AUTOCORRECTION_MIN_LENGTH < 4
# error Minimum Length is too short and may cause overflows
# endif
@@ -17,6 +24,138 @@
# error Dictionary size excees maximum size permitted
# 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 userspace_config.autocorrection;
+}
+
+/**
+ * @brief Enables autocorrect and saves state to eeprom
+ *
+ */
+void autocorrect_enable(void) {
+ userspace_config.autocorrection = true;
+ eeconfig_update_user(userspace_config.raw);
+}
+
+/**
+ * @brief Disables autocorrect and saves state to eeprom
+ *
+ */
+void autocorrect_disable(void) {
+ userspace_config.autocorrection = false;
+ typo_buffer_size = 0;
+ eeconfig_update_user(userspace_config.raw);
+}
+
+/**
+ * @brief Toggles autocorrect's status and save state to eeprom
+ *
+ */
+void autocorrect_toggle(void) {
+ userspace_config.autocorrection = !userspace_config.autocorrection;
+ typo_buffer_size = 0;
+ eeconfig_update_user(userspace_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
*
@@ -25,131 +164,124 @@
* @return true Continue processing keycodes, and send to host
* @return false Stop processing keycodes, and don't send to host
*/
-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;
+bool process_autocorrection(uint16_t keycode, keyrecord_t *record) {
+ uint8_t mods = get_mods();
+#ifndef NO_ACTION_ONESHOT
+ mods |= get_oneshot_mods();
+#endif
- if (keycode == AUTO_CTN) {
- if (record->event.pressed) {
- typo_buffer_size = 0;
- userspace_config.autocorrection ^= 1;
- eeconfig_update_user(userspace_config.raw);
+ 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 (!userspace_config.autocorrection) {
+ if (!autocorrect_is_enabled()) {
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_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;
+ case KC_A ... KC_Z:
+ // process normally
break;
-# endif
-# ifdef SWAP_HANDS_ENABLE
- case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
- if (keycode >= 0x56F0 || record->event.pressed || !record->tap.count) {
- return true;
- }
- keycode &= 0xFF;
+ 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;
-# endif
-# ifndef NO_ACTION_ONESHOT
- case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
- if ((keycode & 0xF) == MOD_LSFT) {
- return true;
- }
-# endif
- default:
- // Disable autocorrection while a mod other than shift is active.
- if (((get_mods() | get_oneshot_mods()) & ~MOD_MASK_SHIFT) != 0) {
- typo_buffer_size = 0;
- return true;
- }
- 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) {
+ 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;
- } 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 || (keycode == KC_MINUS && (get_mods() | get_oneshot_mods()) & MOD_MASK_SHIFT)) {
- typo_buffer_size = 0;
+ case KC_QUOTE:
+ // Treat " (shifted ') as a word boundary.
+ if ((mods & MOD_MASK_SHIFT) != 0) {
+ keycode = KC_SPC;
}
- keycode = KC_SPC;
- } else {
+ 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 >= AUTOCORRECTION_MAX_LENGTH) {
- memmove(typo_buffer, typo_buffer + 1, AUTOCORRECTION_MAX_LENGTH - 1);
- typo_buffer_size = AUTOCORRECTION_MAX_LENGTH - 1;
+ 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 < AUTOCORRECTION_MIN_LENGTH) {
+ if (typo_buffer_size < AUTOCORRECT_MIN_LENGTH) {
return true;
}
- // Check for typo in buffer using a trie stored in `autocorrection_data`.
+ // Check for typo in buffer using a trie stored in `autocorrect_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 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.
+ 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))) {
+ 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(autocorrection_data + state + 1) | pgm_read_byte(autocorrection_data + state + 2) << 8);
+ 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(autocorrection_data + (++state)))) {
+ } else if (!(code = pgm_read_byte(autocorrect_data + (++state)))) {
++state;
}
- code = pgm_read_byte(autocorrection_data + 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 autocorrection.
- const uint8_t backspaces = code & 63;
- for (uint8_t i = 0; i < backspaces; ++i) {
- tap_code(KC_BSPC);
+ 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));
}
- send_string_P((char const*)(autocorrection_data + state + 1));
if (keycode == KC_SPC) {
typo_buffer[0] = KC_SPC;
@@ -166,5 +298,7 @@ bool process_autocorrection(uint16_t keycode, keyrecord_t* record) {
# pragma GCC pop_options
#else
# pragma message "Warning!!! Autocorrect is not corretly setup!"
-bool process_autocorrection(uint16_t keycode, keyrecord_t* record) { return true; }
+bool process_autocorrection(uint16_t keycode, keyrecord_t* record) {
+ return true;
+}
#endif
diff --git a/users/drashna/keyrecords/autocorrection/autocorrection.h b/users/drashna/keyrecords/autocorrection/autocorrection.h
index cea93159ae..8946b91f1f 100644
--- a/users/drashna/keyrecords/autocorrection/autocorrection.h
+++ b/users/drashna/keyrecords/autocorrection/autocorrection.h
@@ -7,4 +7,11 @@
#include "drashna.h"
-bool process_autocorrection(uint16_t keycode, keyrecord_t* record);
+bool process_autocorrection(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);
diff --git a/users/drashna/keyrecords/autocorrection/make_autocorrection_data.py b/users/drashna/keyrecords/autocorrection/make_autocorrection_data.py
index 54fd9ba594..0dd9b78b9c 100755
--- a/users/drashna/keyrecords/autocorrection/make_autocorrection_data.py
+++ b/users/drashna/keyrecords/autocorrection/make_autocorrection_data.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Google LLC
+# Copyright 2021-2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ Each line of the dict file defines one typo and its correction with the syntax
Example:
:thier -> their
+ dosen't -> doesn't
fitler -> filter
lenght -> length
ouput -> output
@@ -42,7 +43,7 @@ https://getreuer.info/posts/keyboards/autocorrection
import sys
import textwrap
-from typing import Any, Dict, List, Tuple
+from typing import Any, Dict, Iterator, List, Tuple
try:
from english_words import english_words_lower_alpha_set as CORRECT_WORDS
@@ -51,85 +52,67 @@ except ImportError:
'correctly spelled word. To check for this, install the english_words '
'package and rerun this script:\n\n pip install english_words\n')
# Use a minimal word list as a fallback.
- CORRECT_WORDS = ('information', 'available', 'international', 'language',
- 'loosest', 'reference', 'wealthier', 'entertainment',
- 'association', 'provides', 'technology', 'statehood')
+ CORRECT_WORDS = ('apparent', 'association', 'available', 'classification',
+ 'effect', 'entertainment', 'fantastic', 'information',
+ 'integrate', 'international', 'language', 'loosest',
+ 'manual', 'nothing', 'provides', 'reference', 'statehood',
+ 'technology', 'virtually', 'wealthier', 'wonderful')
KC_A = 4
KC_SPC = 0x2c
+KC_QUOT = 0x34
+
+TYPO_CHARS = dict(
+ [
+ ("'", KC_QUOT),
+ (':', KC_SPC), # "Word break" character.
+ ] +
+ # Characters a-z.
+ [(chr(c), c + KC_A - ord('a')) for c in range(ord('a'), ord('z') + 1)]
+)
+
def parse_file(file_name: str) -> List[Tuple[str, str]]:
"""Parses autocorrections dictionary file.
Each line of the file defines one typo and its correction with the syntax
"typo -> correction". Blank lines or lines starting with '#' are ignored. The
- function validates that typos only have characters a-z and that typos are not
- substrings of other typos, otherwise the longer typo would never trigger.
+ function validates that typos only have characters in TYPO_CHARS, that
+ typos are not substrings of other typos, and checking that typos don't trigger
+ on CORRECT_WORDS.
Args:
file_name: String, path of the autocorrections dictionary.
Returns:
List of (typo, correction) tuples.
"""
-
+ correct_words = ('information', 'available', 'international', 'language', 'loosest', 'reference', 'wealthier', 'entertainment', 'association', 'provides', 'technology', 'statehood')
autocorrections = []
typos = set()
- line_number = 0
- for line in open(file_name, 'rt'):
- line_number += 1
- line = line.strip()
- if line and line[0] != '#':
- # Parse syntax "typo -> correction", using strip to ignore indenting.
- tokens = [token.strip() for token in line.split('->', 1)]
- if len(tokens) != 2 or not tokens[0]:
- print(f'Error:{line_number}: Invalid syntax: "{line}"')
+ for line_number, typo, correction in parse_file_lines(file_name):
+ if typo in typos:
+ print(f'Warning:{line_number}: Ignoring duplicate typo: "{typo}"')
+ continue
+
+ # Check that `typo` is valid.
+ if not(all([c in TYPO_CHARS for c in typo])):
+ print(f'Error:{line_number}: Typo "{typo}" has '
+ 'characters other than ' + ''.join(TYPO_CHARS.keys()))
+ sys.exit(1)
+ for other_typo in typos:
+ if typo in other_typo or other_typo in typo:
+ print(f'Error:{line_number}: Typos may not be substrings of one '
+ f'another, otherwise the longer typo would never trigger: '
+ f'"{typo}" vs. "{other_typo}".')
sys.exit(1)
+ if len(typo) < 5:
+ print(f'Warning:{line_number}: It is suggested that typos are at '
+ f'least 5 characters long to avoid false triggers: "{typo}"')
- typo, correction = tokens
- typo = typo.lower() # Force typos to lowercase.
- typo = typo.replace(' ', ':')
-
- if typo in typos:
- print(f'Warning:{line_number}: Ignoring duplicate typo: "{typo}"')
- continue
+ check_typo_against_dictionary(typo, line_number, correct_words)
- # Check that `typo` is valid.
- if not(all([ord('a') <= ord(c) <= ord('z') or c == ':' for c in typo])):
- print(f'Error:{line_number}: Typo "{typo}" has '
- 'characters other than a-z and :.')
- sys.exit(1)
- for other_typo in typos:
- if typo in other_typo or other_typo in typo:
- print(f'Error:{line_number}: Typos may not be substrings of one '
- f'another, otherwise the longer typo would never trigger: '
- f'"{typo}" vs. "{other_typo}".')
- sys.exit(1)
- if len(typo) < 5:
- print(f'Warning:{line_number}: It is suggested that typos are at '
- f'least 5 characters long to avoid false triggers: "{typo}"')
-
- if typo.startswith(':') and typo.endswith(':'):
- if typo[1:-1] in CORRECT_WORDS:
- print(f'Warning:{line_number}: Typo "{typo}" is a correctly spelled '
- 'dictionary word.')
- elif typo.startswith(':') and not typo.endswith(':'):
- for word in CORRECT_WORDS:
- if word.startswith(typo[1:]):
- print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger '
- f'on correctly spelled word "{word}".')
- elif not typo.startswith(':') and typo.endswith(':'):
- for word in CORRECT_WORDS:
- if word.endswith(typo[:-1]):
- print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger '
- f'on correctly spelled word "{word}".')
- elif not typo.startswith(':') and not typo.endswith(':'):
- for word in CORRECT_WORDS:
- if typo in word:
- print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger '
- f'on correctly spelled word "{word}".')
-
- autocorrections.append((typo, correction))
- typos.add(typo)
+ autocorrections.append((typo, correction))
+ typos.add(typo)
return autocorrections
@@ -152,6 +135,47 @@ def make_trie(autocorrections: List[Tuple[str, str]]) -> Dict[str, Any]:
return trie
+def parse_file_lines(file_name: str) -> Iterator[Tuple[int, str, str]]:
+ """Parses lines read from `file_name` into typo-correction pairs."""
+
+ line_number = 0
+ for line in open(file_name, 'rt'):
+ line_number += 1
+ line = line.strip()
+ if line and line[0] != '#':
+ # Parse syntax "typo -> correction", using strip to ignore indenting.
+ tokens = [token.strip() for token in line.split('->', 1)]
+ if len(tokens) != 2 or not tokens[0]:
+ print(f'Error:{line_number}: Invalid syntax: "{line}"')
+ sys.exit(1)
+
+ typo, correction = tokens
+ typo = typo.lower() # Force typos to lowercase.
+ typo = typo.replace(' ', ':')
+
+ yield line_number, typo, correction
+
+
+def check_typo_against_dictionary(typo: str, line_number: int, correct_words) -> None:
+ """Checks `typo` against English dictionary words."""
+
+ if typo.startswith(':') and typo.endswith(':'):
+ if typo[1:-1] in correct_words:
+ print(f'Warning:{line_number}: Typo "{typo}" is a correctly spelled dictionary word.')
+ elif typo.startswith(':') and not typo.endswith(':'):
+ for word in correct_words:
+ if word.startswith(typo[1:]):
+ print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger on correctly spelled word "{word}".')
+ elif not typo.startswith(':') and typo.endswith(':'):
+ for word in correct_words:
+ if word.endswith(typo[:-1]):
+ print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger on correctly spelled word "{word}".')
+ elif not typo.startswith(':') and not typo.endswith(':'):
+ for word in correct_words:
+ if typo in word:
+ print(f'Warning:{line_number}: Typo "{typo}" would falsely trigger on correctly spelled word "{word}".')
+
+
def serialize_trie(autocorrections: List[Tuple[str, str]],
trie: Dict[str, Any]) -> List[int]:
"""Serializes trie and correction data in a form readable by the C code.
@@ -165,7 +189,7 @@ def serialize_trie(autocorrections: List[Tuple[str, str]],
table = []
# Traverse trie in depth first order.
- def traverse(trie_node):
+ def traverse(trie_node: Dict[str, Any]) -> Dict[str, Any]:
if 'LEAF' in trie_node: # Handle a leaf trie node.
typo, correction = trie_node['LEAF']
word_boundary_ending = typo[-1] == ':'
@@ -200,37 +224,35 @@ def serialize_trie(autocorrections: List[Tuple[str, str]],
traverse(trie)
- def serialize(e):
- def kc_code(c):
- if ord('a') <= ord(c) <= ord('z'):
- return ord(c) - ord('a') + KC_A
- elif c == ':':
- return KC_SPC
- else:
- raise ValueError(f'Invalid character: {c}')
-
- encode_link = lambda link: [link['byte_offset'] & 255,
- link['byte_offset'] >> 8]
-
+ def serialize(e: Dict[str, Any]) -> List[int]:
if not e['links']: # Handle a leaf table entry.
return e['data']
elif len(e['links']) == 1: # Handle a chain table entry.
- return list(map(kc_code, e['chars'])) + [0] #+ encode_link(e['links'][0]))
+ return [TYPO_CHARS[c] for c in e['chars']] + [0]
else: # Handle a branch table entry.
data = []
for c, link in zip(e['chars'], e['links']):
- data += [kc_code(c) | (0 if data else 64)] + encode_link(link)
+ data += [TYPO_CHARS[c] | (0 if data else 64)] + encode_link(link)
return data + [0]
byte_offset = 0
for e in table: # To encode links, first compute byte offset of each entry.
e['byte_offset'] = byte_offset
byte_offset += len(serialize(e))
- assert 0 <= byte_offset <= 0xffff
return [b for e in table for b in serialize(e)] # Serialize final table.
+def encode_link(link: Dict[str, Any]) -> List[int]:
+ """Encodes a node link as two bytes."""
+ byte_offset = link['byte_offset']
+ if not (0 <= byte_offset <= 0xffff):
+ print('Error: The autocorrection table is too large, a node link exceeds '
+ '64KB limit. Try reducing the autocorrection dict to fewer entries.')
+ sys.exit(1)
+ return [byte_offset & 255, byte_offset >> 8]
+
+
def write_generated_code(autocorrections: List[Tuple[str, str]],
data: List[int],
file_name: str) -> None:
@@ -242,7 +264,10 @@ def write_generated_code(autocorrections: List[Tuple[str, str]],
file_name: String, path of the output C file.
"""
assert all(0 <= b <= 255 for b in data)
- typo_len = lambda e: len(e[0])
+
+ def typo_len(e: Tuple[str, str]) -> int:
+ return len(e[0])
+
min_typo = min(autocorrections, key=typo_len)[0]
max_typo = max(autocorrections, key=typo_len)[0]
generated_code = ''.join([
@@ -252,9 +277,8 @@ def write_generated_code(autocorrections: List[Tuple[str, str]],
for typo, correction in autocorrections)),
f'\n#define AUTOCORRECTION_MIN_LENGTH {len(min_typo)} // "{min_typo}"\n',
f'#define AUTOCORRECTION_MAX_LENGTH {len(max_typo)} // "{max_typo}"\n\n',
- f'#define DICTIONARY_SIZE {len(data)}\n\n',
- textwrap.fill('static const uint8_t autocorrection_data[DICTIONARY_SIZE] PROGMEM = {%s};' % (
- ', '.join(map(str, data))), width=120, subsequent_indent=' '),
+ textwrap.fill('static const uint8_t autocorrection_data[%d] PROGMEM = {%s};' % (
+ len(data), ', '.join(map(str, data))), width=80, subsequent_indent=' '),
'\n\n'])
with open(file_name, 'wt') as f:
diff --git a/users/drashna/keyrecords/caps_word.c b/users/drashna/keyrecords/caps_word.c
deleted file mode 100644
index a152b2387b..0000000000
--- a/users/drashna/keyrecords/caps_word.c
+++ /dev/null
@@ -1,139 +0,0 @@
-#include "caps_word.h"
-
-static bool caps_word_active = false;
-
-#if CAPS_WORD_IDLE_TIMEOUT > 0
-# if CAPS_WORD_IDLE_TIMEOUT < 100 || CAPS_WORD_IDLE_TIMEOUT > 30000
-// Constrain timeout to a sensible range. With the 16-bit timer, the longest
-// representable timeout is 32768 ms, rounded here to 30000 ms = half a minute.
-# error "caps_word: CAPS_WORD_IDLE_TIMEOUT must be between 100 and 30000 ms"
-# endif
-
-static uint16_t idle_timer = 0;
-
-void caps_word_task(void) {
- if (caps_word_active && timer_expired(timer_read(), idle_timer)) {
- caps_word_set(false);
- }
-}
-#endif // CAPS_WORD_IDLE_TIMEOUT > 0
-
-bool process_caps_word(uint16_t keycode, keyrecord_t* record) {
-#ifndef NO_ACTION_ONESHOT
- const uint8_t mods = get_mods() | get_oneshot_mods();
-#else
- const uint8_t mods = get_mods();
-#endif // NO_ACTION_ONESHOT
-
- if (!caps_word_active) {
- // Pressing both shift keys at the same time enables caps word.
- if ((mods & MOD_MASK_SHIFT) == MOD_MASK_SHIFT) {
- caps_word_set(true); // Activate Caps Word.
- return false;
- }
- return true;
- } else {
-#if CAPS_WORD_IDLE_TIMEOUT > 0
- idle_timer = record->event.time + CAPS_WORD_IDLE_TIMEOUT;
-#endif // CAPS_WORD_IDLE_TIMEOUT > 0
- }
-
- if (!record->event.pressed) {
- return true;
- }
-
- if (!(mods & ~MOD_MASK_SHIFT)) {
- switch (keycode) {
- // Ignore MO, TO, TG, TT, and OSL layer switch keys.
- case QK_MOMENTARY ... QK_MOMENTARY_MAX:
- case QK_TO ... QK_TO_MAX:
- case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
- case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
- case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
- return true;
-
-#ifndef NO_ACTION_TAPPING
- case QK_MOD_TAP ... QK_MOD_TAP_MAX:
- if (record->tap.count == 0) {
- // Deactivate if a mod becomes active through holding a mod-tap key.
- caps_word_set(false);
- return true;
- }
- keycode &= 0xff;
- break;
-
-# ifndef NO_ACTION_LAYER
- case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
-# endif // NO_ACTION_LAYER
- if (record->tap.count == 0) {
- return true;
- }
- keycode &= 0xff;
- break;
-#endif // NO_ACTION_TAPPING
-
-#ifdef SWAP_HANDS_ENABLE
- case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
- if (keycode > 0x56F0 || record->tap.count == 0) {
- return true;
- }
- keycode &= 0xff;
- break;
-#endif // SWAP_HANDS_ENABLE
- }
-
- if (caps_word_press_user(keycode)) {
- return true;
- }
- }
-
- caps_word_set(false); // Deactivate Caps Word.
- return true;
-}
-
-void caps_word_set(bool active) {
- if (active != caps_word_active) {
- if (active) {
- clear_mods();
-#ifndef NO_ACTION_ONESHOT
- clear_oneshot_mods();
-#endif // NO_ACTION_ONESHOT
-#if CAPS_WORD_IDLE_TIMEOUT > 0
- idle_timer = timer_read() + CAPS_WORD_IDLE_TIMEOUT;
-#endif // CAPS_WORD_IDLE_TIMEOUT > 0
- } else if ((get_weak_mods() & MOD_BIT(KC_LSFT)) != 0) {
- // If the weak shift mod is still on, turn it off and send an update to
- // the host computer.
- del_weak_mods(MOD_BIT(KC_LSFT));
- send_keyboard_report();
- }
-
- caps_word_active = active;
- caps_word_set_user(active);
- }
-}
-
-bool caps_word_get(void) {
- return caps_word_active;
-}
-
-__attribute__((weak)) void caps_word_set_user(bool active) {}
-
-__attribute__((weak)) bool caps_word_press_user(uint16_t keycode) {
- switch (keycode) {
- // Keycodes that continue Caps Word, with shift applied.
- case KC_A ... KC_Z:
- add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to the next key.
- return true;
-
- // Keycodes that continue Caps Word, without shifting.
- case KC_1 ... KC_0:
- case KC_BSPC:
- case KC_MINS:
- case KC_UNDS:
- return true;
-
- default:
- return false; // Deactivate Caps Word.
- }
-}
diff --git a/users/drashna/keyrecords/caps_word.h b/users/drashna/keyrecords/caps_word.h
deleted file mode 100644
index 4279d7e831..0000000000
--- a/users/drashna/keyrecords/caps_word.h
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2021 Google LLC.
-// SPDX-License-Identifier: Apache-2.0
-
-#pragma once
-
-#include "drashna.h"
-
-// Call this function from `process_record_user()` to implement Caps Word.
-bool process_caps_word(uint16_t keycode, keyrecord_t* record);
-
-// If CAPS_WORD_IDLE_TIMEOUT is set, call `caps_word_task()` from
-// `matrix_scan_user()` as described above.
-//
-// If CAPS_WORD_IDLE_TIMEOUT isn't set, calling this function has no effect (but
-// will still compile).
-#if CAPS_WORD_IDLE_TIMEOUT > 0
-void caps_word_task(void);
-#else
-static inline void caps_word_task(void) {}
-#endif
-
-// Activates or deactivates Caps Word. For instance activate Caps Word with a
-// combo by defining a `COMBO_ACTION` that calls `caps_word_set(true)`:
-//
-// void process_combo_event(uint16_t combo_index, bool pressed) {
-// switch(combo_index) {
-// case CAPS_COMBO:
-// if (pressed) {
-// caps_word_set(true); // Activate Caps Word.
-// }
-// break;
-//
-// // Other combos...
-// }
-// }
-void caps_word_set(bool active);
-
-// Returns whether Caps Word is currently active.
-bool caps_word_get(void);
-
-// An optional callback that gets called when Caps Word turns on or off. This is
-// useful to represent the current Caps Word state, e.g. by setting an LED or
-// playing a sound. In your keymap, define
-//
-// void caps_word_set_user(bool active) {
-// if (active) {
-// // Do something when Caps Word activates.
-// } else {
-// // Do something when Caps Word deactivates.
-// }
-// }
-void caps_word_set_user(bool active);
-
-// An optional callback which is called on every key press while Caps Word is
-// active. When the key should be shifted (that is, a letter key), the callback
-// should call `add_weak_mods(MOD_BIT(KC_LSFT))` to shift the key. The callback
-// also determines whether the key should continue Caps Word. Returning true
-// continues the current "word", while returning false is "word breaking" and
-// deactivates Caps Word. The default callback is
-//
-// bool caps_word_press_user(uint16_t keycode) {
-// switch (keycode) {
-// // Keycodes that continue Caps Word, with shift applied.
-// case KC_A ... KC_Z:
-// add_weak_mods(MOD_BIT(KC_LSFT)); // Apply shift to the next key.