diff --git a/combos.h b/combos.h index 9e8c27c..72006ae 100644 --- a/combos.h +++ b/combos.h @@ -21,11 +21,11 @@ COMBO_DEF(c_and, HM_I, KC_DOT); * Alternate symbols. */ COMBO_DEF(c_grave, KC_W, HM_R); -COMBO_DEF(c_equals, KC_P, KC_T); +COMBO_DEF(c_underscore, KC_P, KC_T); COMBO_DEF(c_backslash, KC_F, HM_S); COMBO_DEF(c_tilde, KC_B, KC_G); -COMBO_DEF(c_underscore, KC_M, KC_J); +COMBO_DEF(c_equals, KC_M, KC_J); COMBO_DEF(c_hyphen, KC_L, HM_N); COMBO_DEF(c_slash, KC_U, HM_E); COMBO_DEF(c_pipe, KC_Y, HM_I); diff --git a/features/sentence_case.c b/features/sentence_case.c new file mode 100644 index 0000000..60dbb9f --- /dev/null +++ b/features/sentence_case.c @@ -0,0 +1,346 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file sentence_case.c + * @brief Sentence Case implementation + * + * For full documentation, see + * + */ + +#include "sentence_case.h" + +#include + +#if !defined(IS_QK_MOD_TAP) +// Attempt to detect out-of-date QMK installation, which would fail with +// implicit-function-declaration errors in the code below. +#error "sentence_case: QMK version is too old to build. Please update QMK." +#elif defined(NO_ACTION_ONESHOT) +// One-shot keys must be enabled for Sentence Case. One-shot keys are enabled +// by default, but are disabled by `#define NO_ACTION_ONESHOT` in config.h. If +// your config.h includes such a line, please remove it. +#error "sentence_case: Please enable oneshot." +#else + +// Number of keys of state history to retain for backspacing. +#define STATE_HISTORY_SIZE 6 + +// clang-format off +/** States in matching the beginning of a sentence. */ +enum { + STATE_INIT, /**< Initial enabled state. */ + STATE_WORD, /**< Within a word. */ + STATE_ABBREV, /**< Within an abbreviation like "e.g.". */ + STATE_ENDING, /**< Sentence ended. */ + STATE_PRIMED, /**< "Primed" state, in the space following an ending. */ + STATE_DISABLED, /**< Sentence Case is disabled. */ +}; +// clang-format on + +#if SENTENCE_CASE_TIMEOUT > 0 +static uint16_t idle_timer = 0; +#endif // SENTENCE_CASE_TIMEOUT > 0 +#if SENTENCE_CASE_BUFFER_SIZE > 1 +static uint16_t key_buffer[SENTENCE_CASE_BUFFER_SIZE] = {0}; +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 +static uint8_t state_history[STATE_HISTORY_SIZE]; +static uint16_t suppress_key = KC_NO; +static uint8_t sentence_state = STATE_INIT; + +// Sets the current state to `new_state`. +static void set_sentence_state(uint8_t new_state) { +#ifndef NO_DEBUG + if (debug_enable && sentence_state != new_state) { + static const char* state_names[] = { + "INIT", "WORD", "ABBREV", "ENDING", "PRIMED", "DISABLED", + }; + dprintf("Sentence case: %s\n", state_names[new_state]); + } +#endif // NO_DEBUG + + const bool primed = (new_state == STATE_PRIMED); + if (primed != (sentence_state == STATE_PRIMED)) { + sentence_case_primed(primed); + } + sentence_state = new_state; +} + +static void clear_state_history(void) { +#if SENTENCE_CASE_TIMEOUT > 0 + idle_timer = 0; +#endif // SENTENCE_CASE_TIMEOUT > 0 + memset(state_history, STATE_INIT, sizeof(state_history)); + if (sentence_state != STATE_DISABLED) { + set_sentence_state(STATE_INIT); + } +} + +void sentence_case_clear(void) { + clear_state_history(); + suppress_key = KC_NO; +#if SENTENCE_CASE_BUFFER_SIZE > 1 + memset(key_buffer, 0, sizeof(key_buffer)); +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 +} + +void sentence_case_on(void) { + if (sentence_state == STATE_DISABLED) { + sentence_state = STATE_INIT; + sentence_case_clear(); + } +} + +void sentence_case_off(void) { + if (sentence_state != STATE_DISABLED) { + set_sentence_state(STATE_DISABLED); + } +} + +void sentence_case_toggle(void) { + if (sentence_state != STATE_DISABLED) { + sentence_case_off(); + } else { + sentence_case_on(); + } +} + +bool is_sentence_case_on(void) { return sentence_state != STATE_DISABLED; } + +#if SENTENCE_CASE_TIMEOUT > 0 +#if SENTENCE_CASE_TIMEOUT < 100 || SENTENCE_CASE_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 "sentence_case: SENTENCE_CASE_TIMEOUT must be between 100 and 30000 ms" +#endif + +void sentence_case_task(void) { + if (idle_timer && timer_expired(timer_read(), idle_timer)) { + clear_state_history(); // Timed out; clear all state. + } +} +#endif // SENTENCE_CASE_TIMEOUT > 0 + +bool process_sentence_case(uint16_t keycode, keyrecord_t* record) { + // Only process while enabled, and only process press events. + if (sentence_state == STATE_DISABLED || !record->event.pressed) { + return true; + } + +#if SENTENCE_CASE_TIMEOUT > 0 + idle_timer = (record->event.time + SENTENCE_CASE_TIMEOUT) | 1; +#endif // SENTENCE_CASE_TIMEOUT > 0 + + switch (keycode) { +#ifndef NO_ACTION_TAPPING + case QK_MOD_TAP ... QK_MOD_TAP_MAX: + if (record->tap.count == 0) { + return true; + } + keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); + 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 = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); + break; +#endif // NO_ACTION_TAPPING + +#ifdef SWAP_HANDS_ENABLE + case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: + if (IS_SWAP_HANDS_KEYCODE(keycode) || record->tap.count == 0) { + return true; + } + keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode); + break; +#endif // SWAP_HANDS_ENABLE + } + + if (keycode == KC_BSPC) { + // Backspace key pressed. Rewind the state and key buffers. + set_sentence_state(state_history[STATE_HISTORY_SIZE - 1]); + + memmove(state_history + 1, state_history, STATE_HISTORY_SIZE - 1); + state_history[0] = STATE_INIT; +#if SENTENCE_CASE_BUFFER_SIZE > 1 + memmove(key_buffer + 1, key_buffer, + (SENTENCE_CASE_BUFFER_SIZE - 1) * sizeof(uint16_t)); + key_buffer[0] = KC_NO; +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 + return true; + } + + const uint8_t mods = get_mods() | get_weak_mods() | get_oneshot_mods(); + uint8_t new_state = STATE_INIT; + + // We search for sentence beginnings using a simple finite state machine. It + // matches things like "a. a" and "a. a" but not "a.. a" or "a.a. a". The + // state transition matrix is: + // + // 'a' '.' ' ' '\'' + // +------------------------------------- + // INIT | WORD INIT INIT INIT + // WORD | WORD ENDING INIT WORD + // ABBREV | ABBREV ABBREV INIT ABBREV + // ENDING | ABBREV INIT PRIMED ENDING + // PRIMED | match! INIT PRIMED PRIMED + char code = sentence_case_press_user(keycode, record, mods); + dprintf("Sentence Case: code = '%c' (%d)\n", code, (int)code); + switch (code) { + case '\0': // Current key should be ignored. + return true; + + case 'a': // Current key is a letter. + switch (sentence_state) { + case STATE_ABBREV: + case STATE_ENDING: + new_state = STATE_ABBREV; + break; + + case STATE_PRIMED: + // This is the start of a sentence. + if (keycode != suppress_key) { + suppress_key = keycode; + set_oneshot_mods(MOD_BIT(KC_LSFT)); // Shift mod to capitalize. + new_state = STATE_WORD; + } + break; + + default: + new_state = STATE_WORD; + } + break; + + case '.': // Current key is sentence-ending punctuation. + switch (sentence_state) { + case STATE_WORD: + new_state = STATE_ENDING; + break; + + default: + new_state = STATE_ABBREV; + } + break; + + case ' ': // Current key is a space. + if (sentence_state == STATE_PRIMED || + (sentence_state == STATE_ENDING +#if SENTENCE_CASE_BUFFER_SIZE > 1 + && sentence_case_check_ending(key_buffer) +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 + )) { + new_state = STATE_PRIMED; + suppress_key = KC_NO; + } + break; + + case '\'': // Current key is a quote. + new_state = sentence_state; + break; + } + + // Slide key_buffer and state_history buffers one element to the left. + // Optimization note: Using manual loops instead of memmove() here saved + // ~100 bytes on AVR. +#if SENTENCE_CASE_BUFFER_SIZE > 1 + for (int8_t i = 0; i < SENTENCE_CASE_BUFFER_SIZE - 1; ++i) { + key_buffer[i] = key_buffer[i + 1]; + } +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 + for (int8_t i = 0; i < STATE_HISTORY_SIZE - 1; ++i) { + state_history[i] = state_history[i + 1]; + } + +#if SENTENCE_CASE_BUFFER_SIZE > 1 + key_buffer[SENTENCE_CASE_BUFFER_SIZE - 1] = keycode; + if (new_state == STATE_ENDING && !sentence_case_check_ending(key_buffer)) { + dprintf("Not a real ending.\n"); + new_state = STATE_INIT; + } +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 + state_history[STATE_HISTORY_SIZE - 1] = sentence_state; + + set_sentence_state(new_state); + return true; +} + +bool sentence_case_just_typed_P(const uint16_t* buffer, const uint16_t* pattern, + int8_t pattern_len) { +#if SENTENCE_CASE_BUFFER_SIZE > 1 + buffer += SENTENCE_CASE_BUFFER_SIZE - pattern_len; + for (int8_t i = 0; i < pattern_len; ++i) { + if (buffer[i] != pgm_read_word(pattern + i)) { + return false; + } + } + return true; +#else + return false; +#endif // SENTENCE_CASE_BUFFER_SIZE > 1 +} + +__attribute__((weak)) bool sentence_case_check_ending(const uint16_t* buffer) { +#if SENTENCE_CASE_BUFFER_SIZE >= 5 + // Don't consider the abbreviations "vs." and "etc." to end the sentence. + if (SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT) || + SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_E, KC_T, KC_C, KC_DOT)) { + return false; // Not a real sentence ending. + } +#endif // SENTENCE_CASE_BUFFER_SIZE >= 5 + return true; // Real sentence ending; capitalize next letter. +} + +__attribute__((weak)) char sentence_case_press_user(uint16_t keycode, + keyrecord_t* record, + uint8_t mods) { + if ((mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { + const bool shifted = mods & MOD_MASK_SHIFT; + switch (keycode) { + case KC_LCTL ... KC_RGUI: // Mod keys. + return '\0'; // These keys are ignored. + + case KC_A ... KC_Z: + return 'a'; // Letter key. + + case KC_DOT: // . is punctuation, Shift . is a symbol (>) + return !shifted ? '.' : '#'; + case KC_1: + case KC_SLSH: + return shifted ? '.' : '#'; + case KC_2 ... KC_0: // 2 3 4 5 6 7 8 9 0 + case KC_MINS ... KC_SCLN: // - = [ ] ; backslash + case KC_GRV: + case KC_COMM: + return '#'; // Symbol key. + + case KC_SPC: + return ' '; // Space key. + + case KC_QUOT: + return '\''; // Quote key. + } + } + + // Otherwise clear Sentence Case to initial state. + sentence_case_clear(); + return '\0'; +} + +__attribute__((weak)) void sentence_case_primed(bool primed) {} + +#endif // NO_ACTION_ONESHOT diff --git a/features/sentence_case.h b/features/sentence_case.h new file mode 100644 index 0000000..5b1ebbc --- /dev/null +++ b/features/sentence_case.h @@ -0,0 +1,224 @@ +// Copyright 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. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @file sentence_case.h + * @brief Sentence case: automatically capitalize the first letter of sentences. + * + * This library automatically capitalizes the first letter of sentences, + * reducing the need to explicitly use shift. To use it, you simply type as + * usual but without shifting at the start of sentences. The feature detects + * when new sentences begin and capitalizes automatically. + * + * Sentence Case matches patterns like + * + * "a. a" + * "a. a" + * "a? a" + * "a!' 'a" + * + * but not + * + * "a... a" + * "a.a. a" + * + * Additionally by default, abbreviations "vs." and "etc." are exceptionally + * detected as not real sentence endings. You can use the callback + * `sentence_case_check_ending()` to define other exceptions. + * + * @note One-shot keys must be enabled. + * + * For full documentation, see + * + */ + +#pragma once + +#include "quantum.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// The size of the keycode buffer for `sentence_case_check_ending()`. It must be +// at least as large as the longest pattern checked. If less than 2, buffering +// is disabled and the callback is not called. +#ifndef SENTENCE_CASE_BUFFER_SIZE +#define SENTENCE_CASE_BUFFER_SIZE 8 +#endif // SENTENCE_CASE_BUFFER_SIZE + +/** + * Handler function for Sentence Case. + * + * Call this function from `process_record_user()` to implement Sentence Case. + */ +bool process_sentence_case(uint16_t keycode, keyrecord_t* record); + +/** + * @fn sentence_case_task(void) + * Matrix task function for Sentence Case. + * + * If using `SENTENCE_CASE_TIMEOUT`, call this function from your + * `matrix_scan_user()` function in keymap.c. (If no timeout is set, calling + * `sentence_case_task()` has no effect.) + */ +#if SENTENCE_CASE_TIMEOUT > 0 +void sentence_case_task(void); +#else +static inline void sentence_case_task(void) {} +#endif + +void sentence_case_on(void); /**< Enables Sentence Case. */ +void sentence_case_off(void); /**< Disables Sentence Case. */ +void sentence_case_toggle(void); /**< Toggles Sentence Case. */ +bool is_sentence_case_on(void); /**< Gets whether currently enabled. */ +void sentence_case_clear(void); /**< Clears Sentence Case to initial state. */ + +/** + * Optional callback to indicate primed state. + * + * This callback gets called when Sentence Case changes to or from a "primed" + * state, useful to indicate with an LED or otherwise that the next letter typed + * will be capitalized. + */ +void sentence_case_primed(bool primed); + +/** + * Optional callback to determine whether there is a real sentence ending. + * + * When a sentence-ending punctuation key is typed, this callback is called to + * determine whether it is a real sentence ending, meaning the first letter of + * the following word should be capitalized. For instance, abbreviations like + * "vs." are usually not real sentence endings. The input argument is a buffer + * of the last SENTENCE_CASE_BUFFER_SIZE keycodes. Returning true means it is a + * real sentence ending; returning false means it is not. + * + * The default implementation checks for the abbreviations "vs." and "etc.": + * + * bool sentence_case_check_ending(const uint16_t* buffer) { + * // Don't consider "vs." and "etc." to end the sentence. + * if (SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT) || + * SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_E, KC_T, KC_C, KC_DOT)) { + * return false; // Not a real sentence ending. + * } + * return true; // Real sentence ending; capitalize next letter. + * } + * + * @note This callback is used only if `SENTENCE_CASE_BUFFER_SIZE >= 2`. + * Otherwise it has no effect. + * + * @param buffer Buffer of the last `SENTENCE_CASE_BUFFER_SIZE` keycodes. + * @return whether there is a real sentence ending. + */ +bool sentence_case_check_ending(const uint16_t* buffer); + +/** + * Macro to be used in `sentence_case_check_ending()`. + * + * Returns true if a given pattern of keys was just typed by comparing with the + * keycode buffer. This is useful for defining exceptions in + * `sentence_case_check_ending()`. + * + * For example, `SENTENCE_CASE_JUST_TYPED(KC_SPC, KC_V, KC_S, KC_DOT)` returns + * true if " vs." were the last four keys typed. + * + * @note The pattern must be no longer than `SENTENCE_CASE_BUFFER_SIZE`. + */ +#define SENTENCE_CASE_JUST_TYPED(...) \ + ({ \ + static const uint16_t PROGMEM pattern[] = {__VA_ARGS__}; \ + sentence_case_just_typed_P(buffer, pattern, \ + sizeof(pattern) / sizeof(uint16_t)); \ + }) +bool sentence_case_just_typed_P(const uint16_t* buffer, const uint16_t* pattern, + int8_t pattern_len); + +/** + * Optional callback defining which keys are letter, punctuation, etc. + * + * This callback may be useful if you type non-US letters or have customized the + * shift behavior of the punctuation keys. The return value tells Sentence Case + * how to interpret the key: + * + * 'a' Key is a letter, by default KC_A to KC_Z. If occurring at the start of + * a sentence, Sentence Case applies shift to capitalize it. + * + * '.' Key is sentence-ending punctuation. Default: . ? ! + * + * '#' Key types a backspaceable character that isn't part of a word. + * Default: - = [ ] ; ' ` , < > / digits backslash + * + * ' ' Key is a space. Default: KC_SPC + * + * '\'' Key is a quote or double quote character. Default: KC_QUOT. + * + * '\0' Sentence Case should ignore this key. + * + * If a hotkey or navigation key is pressed (or another key that performs an + * action that backspace doesn't undo), then the callback should call + * `sentence_case_clear()` to clear the state and then return '\0'. + * + * The default callback is: + * + * char sentence_case_press_user(uint16_t keycode, + * keyrecord_t* record, + * uint8_t mods) { + * if ((mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { + * const bool shifted = mods & MOD_MASK_SHIFT; + * switch (keycode) { + * case KC_LCTL ... KC_RGUI: // Mod keys. + * return '\0'; // These keys are ignored. + * + * case KC_A ... KC_Z: + * return 'a'; // Letter key. + * + * case KC_DOT: // . is punctuation, Shift . is a symbol (>) + * return !shifted ? '.' : '#'; + * case KC_1: + * case KC_SLSH: + * return shifted ? '.' : '#'; + * case KC_2 ... KC_0: // 2 3 4 5 6 7 8 9 0 + * case KC_MINS ... KC_SCLN: // - = [ ] ; backslash + * case KC_GRV: + * case KC_COMM: + * return '#'; // Symbol key. + * + * case KC_SPC: + * return ' '; // Space key. + * + * case KC_QUOT: + * return '\''; // Quote key. + * } + * } + * + * // Otherwise clear Sentence Case to initial state. + * sentence_case_clear(); + * return '\0'; + * } + * + * To customize, copy the above function into your keymap and add/remove + * keycodes to the above cases. + * + * @param keycode Current keycode. + * @param record record_t for the current press event. + * @param mods equal to `get_mods() | get_weak_mods() | get_oneshot_mods()` + * @return char code 'a', '.', '#', ' ', or '\0' indicating how the key is to be + * interpreted as described above. + */ +char sentence_case_press_user(uint16_t keycode, keyrecord_t* record, + uint8_t mods); + +#ifdef __cplusplus +} +#endif diff --git a/flmng0.c b/flmng0.c index 70a4571..05ac453 100644 --- a/flmng0.c +++ b/flmng0.c @@ -9,6 +9,7 @@ #include "quantum.h" #include "features/custom_shift_keys.h" +#include "features/sentence_case.h" #include "hrm.h" #include "combos.h" @@ -94,6 +95,15 @@ const custom_shift_key_t custom_shift_keys[] = { }; uint8_t NUM_CUSTOM_SHIFT_KEYS = sizeof(custom_shift_keys) / sizeof(custom_shift_key_t); + +#define RE_HOLD(r, original, new) \ + case original: \ + if (r->event.pressed && !r->tap.count) { \ + tap_code16(new); \ + return false; \ + } \ + break; + bool process_record_user(uint16_t keycode, keyrecord_t *record) { // Disable one shot mods for non-base keys if (record->event.pressed) { @@ -115,6 +125,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { if (!process_hrm(keycode, record)) { return false; } if (!process_custom_shift_keys(keycode, record)) { return false; } + if (!process_sentence_case(keycode, record)) { return false; } const uint8_t mods = get_mods(); const uint8_t all_mods = get_mods() | get_weak_mods() | get_oneshot_mods(); @@ -129,6 +140,15 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } break; + RE_HOLD(record, NV_LEFT, KC_HOME) + RE_HOLD(record, NV_UP, C(KC_PGUP)) + RE_HOLD(record, NV_DOWN, C(KC_PGDN)) + RE_HOLD(record, NV_RGHT, KC_END) + + case START_SENTENCE_CASE: + sentence_case_toggle(); + return false; + case TH_LH: if (record->tap.count > 0) { if (record->event.pressed) { diff --git a/keymaps/combo.json b/keymaps/combo.json index 4ac7597..b28551e 100644 --- a/keymaps/combo.json +++ b/keymaps/combo.json @@ -6,11 +6,18 @@ "keymap": "flmng0", "layout": "LAYOUT_a_dux_wrap", "layers": [ - [ "_BASE" ], - [ "_NAV" ], - [ "_SYM" ], - [ "_NUM" ], - [ "_GAME" ] + [ + "_BASE" + ], + [ + "_NAV" + ], + [ + "_NUM" + ], + [ + "_GAME" + ] ], "author": "Timothy Davis" -} +} \ No newline at end of file diff --git a/layout.h b/layout.h index 7417fa4..27f7d09 100644 --- a/layout.h +++ b/layout.h @@ -2,24 +2,33 @@ #include "quantum_keycodes.h" +enum USER_KEYS { + START_SENTENCE_CASE = SAFE_RANGE, +}; + +#define UK_SENT START_SENTENCE_CASE + #define ID_BASE 0 #define ID_NAV 1 -#define ID_SYM 2 -#define ID_NUM 3 -#define ID_GAME 4 +#define ID_NUM 2 +#define ID_GAME 3 #define D_BASE DF(ID_BASE) #define D_GAME DF(ID_GAME) #define LT_NAV(code) LT(ID_NAV, code) -#define LT_SYM(code) LT(ID_SYM, code) #define LT_NUM(code) LT(ID_NUM, code) #define TH_LA LT_NUM(KC_TAB) #define TH_LH LT_NAV(KC_SPACE) #define TH_RH OSM(MOD_LSFT) -#define TH_RA LT_SYM(KC_ENTER) +#define TH_RA KC_ENTER + +#define NV_LEFT LT(0, KC_LEFT) +#define NV_RGHT LT(0, KC_RGHT) +#define NV_UP LT(0, KC_UP) +#define NV_DOWN LT(0, KC_DOWN) #define HM_A LGUI_T(KC_A) #define HM_R LALT_T(KC_R) @@ -38,16 +47,18 @@ TH_LA , TH_LH , TH_RH , TH_RA #define _NAV \ - KC_ESC , KC_NO , KC_NO , KC_NO , KC_NO , KC_NO , KC_HOME, KC_PGDN, KC_PGUP, KC_END , \ - CW_TOGG, KC_NO , KC_NO , KC_NO , KC_NO , KC_NO , KC_LEFT, KC_DOWN, KC_UP , KC_RGHT, \ - KC_LGUI, KC_LALT, KC_LSFT, KC_LCTL, KC_NO , KC_NO , KC_H , KC_J , KC_K , KC_L , \ + KC_ESC , KC_NO , KC_NO , KC_NO , KC_NO , KC_PGUP, KC_NO , KC_NO , KC_NO , KC_NO , \ + KC_LGUI, KC_LALT, KC_LSFT, KC_LCTL, KC_NO , KC_PGDN, NV_LEFT, NV_DOWN, NV_UP , NV_RGHT, \ + UK_SENT, KC_NO , KC_NO , KC_NO , KC_NO , KC_NO , KC_H , KC_J , KC_K , KC_L , \ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS +/* #define _SYM \ KC_EXLM, KC_AT , KC_HASH, KC_DLR , KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_SLSH, KC_COLN, \ KC_TILD, KC_GRV , KC_DQUO, KC_BSLS, KC_NO , KC_NO , KC_PIPE, KC_PLUS, KC_MINS, KC_QUES, \ KC_LGUI, KC_LALT, KC_LSFT, KC_LCTL, KC_NO , KC_ENT , KC_RCTL, KC_RSFT, KC_LALT, KC_RGUI, \ KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS +*/ #define _NUM \ KC_NO , KC_F7 , KC_F8 , KC_F9 , KC_F12 , KC_UNDS, KC_7 , KC_8 , KC_9 , D_GAME , \ diff --git a/rules.mk b/rules.mk index 8451d4f..d8db1ea 100644 --- a/rules.mk +++ b/rules.mk @@ -2,7 +2,7 @@ COMBO_ENABLE = yes CAPS_WORD_ENABLE = yes INTROSPECTION_KEYMAP_C = flmng0.c -SRC += features/custom_shift_keys.c +SRC += features/custom_shift_keys.c features/sentence_case.c OPT_DEFS += -DHOME_ROW_MODS