diff options
Diffstat (limited to 'src/nvim/vterm/keyboard.c')
-rw-r--r-- | src/nvim/vterm/keyboard.c | 140 |
1 files changed, 103 insertions, 37 deletions
diff --git a/src/nvim/vterm/keyboard.c b/src/nvim/vterm/keyboard.c index 696b09157e..dd088ac40e 100644 --- a/src/nvim/vterm/keyboard.c +++ b/src/nvim/vterm/keyboard.c @@ -10,54 +10,77 @@ # include "vterm/keyboard.c.generated.h" #endif +static VTermKeyEncodingFlags vterm_state_get_key_encoding_flags(const VTermState *state) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + const struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + assert(stack->size > 0); + return stack->items[stack->size - 1]; +} + void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { - // The shift modifier is never important for Unicode characters apart from Space - if (c != ' ') { - mod &= (unsigned)~VTERM_MOD_SHIFT; + bool passthru = false; + if (c == ' ') { + // Space is passed through only when there are no modifiers (including shift) + passthru = mod == VTERM_MOD_NONE; + } else { + // Otherwise pass through when there are no modifiers (ignoring shift) + passthru = (mod & (unsigned)~VTERM_MOD_SHIFT) == 0; } - if (mod == 0) { - // Normal text - ignore just shift + if (passthru) { char str[6]; int seqlen = fill_utf8((int)c, str); vterm_push_output_bytes(vt, str, (size_t)seqlen); return; } - int needs_CSIu; - switch (c) { - // Special Ctrl- letters that can't be represented elsewise - case 'i': - case 'j': - case 'm': - case '[': - needs_CSIu = 1; - break; - // Ctrl-\ ] ^ _ don't need CSUu - case '\\': - case ']': - case '^': - case '_': - needs_CSIu = 0; - break; - // Shift-space needs CSIu - case ' ': - needs_CSIu = !!(mod & VTERM_MOD_SHIFT); - break; - // All other characters needs CSIu except for letters a-z - default: - needs_CSIu = (c < 'a' || c > 'z'); - } + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + if (flags.disambiguate) { + // Always use unshifted codepoint + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; + mod |= VTERM_MOD_SHIFT; + } - // ALT we can just prefix with ESC; anything else requires CSI u - if (needs_CSIu && (mod & (unsigned)~VTERM_MOD_ALT)) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); return; } if (mod & VTERM_MOD_CTRL) { - c &= 0x1f; + // Handle special cases. These are taken from kitty, but seem mostly + // consistent across terminals. + switch (c) { + case '2': + case ' ': + // Ctrl+2 is NUL to match Ctrl+@ (which is Shift+2 on US keyboards) + // Ctrl+Space is also NUL for some reason + c = 0x00; + break; + case '3': + case '4': + case '5': + case '6': + case '7': + // Ctrl+3 through Ctrl+7 are sequential starting from 0x1b. Importantly, + // this means that Ctrl+6 emits 0x1e (the same as Ctrl+^ on US keyboards) + c = 0x1b + c - '3'; + break; + case '8': + // Ctrl+8 is DEL + c = 0x7f; + break; + case '/': + // Ctrl+/ is equivalent to Ctrl+_ for historic reasons + c = 0x1f; + break; + default: + if (c >= '@' && c <= 0x7f) { + c &= 0x1f; + } + break; + } } vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); @@ -75,7 +98,7 @@ typedef struct { KEYCODE_CSINUM, KEYCODE_KEYPAD, } type; - char literal; + int literal; int csinum; } keycodes_s; @@ -137,12 +160,35 @@ static keycodes_s keycodes_kp[] = { { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL }; +static keycodes_s keycodes_kp_csiu[] = { + { KEYCODE_KEYPAD, 57399, 'p' }, // KP_0 + { KEYCODE_KEYPAD, 57400, 'q' }, // KP_1 + { KEYCODE_KEYPAD, 57401, 'r' }, // KP_2 + { KEYCODE_KEYPAD, 57402, 's' }, // KP_3 + { KEYCODE_KEYPAD, 57403, 't' }, // KP_4 + { KEYCODE_KEYPAD, 57404, 'u' }, // KP_5 + { KEYCODE_KEYPAD, 57405, 'v' }, // KP_6 + { KEYCODE_KEYPAD, 57406, 'w' }, // KP_7 + { KEYCODE_KEYPAD, 57407, 'x' }, // KP_8 + { KEYCODE_KEYPAD, 57408, 'y' }, // KP_9 + { KEYCODE_KEYPAD, 57411, 'j' }, // KP_MULT + { KEYCODE_KEYPAD, 57413, 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, 57416, 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, 57412, 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, 57409, 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, 57410, 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, 57414, 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, 57415, 'X' }, // KP_EQUAL +}; + void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) { if (key == VTERM_KEY_NONE) { return; } + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + keycodes_s k; if (key < VTERM_KEY_FUNCTION_0) { if (key >= sizeof(keycodes)/sizeof(keycodes[0])) { @@ -158,7 +204,12 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) { return; } - k = keycodes_kp[key - VTERM_KEY_KP_0]; + + if (flags.disambiguate) { + k = keycodes_kp_csiu[key - VTERM_KEY_KP_0]; + } else { + k = keycodes_kp[key - VTERM_KEY_KP_0]; + } } switch (k.type) { @@ -167,7 +218,9 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_TAB: // Shift-Tab is CSI Z but plain Tab is 0x09 - if (mod == VTERM_MOD_SHIFT) { + if (flags.disambiguate) { + goto case_LITERAL; + } else if (mod == VTERM_MOD_SHIFT) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); } else if (mod & VTERM_MOD_SHIFT) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); @@ -187,7 +240,20 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_LITERAL: case_LITERAL: - if (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) { + if (flags.disambiguate) { + switch (key) { + case VTERM_KEY_TAB: + case VTERM_KEY_ENTER: + case VTERM_KEY_BACKSPACE: + // If there are no mods then leave these as-is + flags.disambiguate = mod != VTERM_MOD_NONE; + break; + default: + break; + } + } + + if (flags.disambiguate) { vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); } else { vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); @@ -229,7 +295,7 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) case KEYCODE_KEYPAD: if (vt->state->mode.keypad) { - k.literal = (char)k.csinum; + k.literal = k.csinum; goto case_SS3; } else { goto case_LITERAL; |