neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

keyboard.c (8945B)


      1 #include <stdio.h>
      2 
      3 #include "nvim/ascii_defs.h"
      4 #include "nvim/tui/termkey/termkey.h"
      5 #include "nvim/vterm/keyboard.h"
      6 #include "nvim/vterm/vterm.h"
      7 #include "nvim/vterm/vterm_internal_defs.h"
      8 
      9 #include "vterm/keyboard.c.generated.h"
     10 
     11 static VTermKeyEncodingFlags vterm_state_get_key_encoding_flags(const VTermState *state)
     12 {
     13  int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
     14  const struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
     15  assert(stack->size > 0);
     16  return stack->items[stack->size - 1];
     17 }
     18 
     19 void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
     20 {
     21  bool passthru = false;
     22  if (c == ' ') {
     23    // Space is passed through only when there are no modifiers (including shift)
     24    passthru = mod == VTERM_MOD_NONE;
     25  } else {
     26    // Otherwise pass through when there are no modifiers (ignoring shift)
     27    passthru = (mod & (unsigned)~VTERM_MOD_SHIFT) == 0;
     28  }
     29 
     30  if (passthru) {
     31    char str[6];
     32    int seqlen = fill_utf8((int)c, str);
     33    vterm_push_output_bytes(vt, str, (size_t)seqlen);
     34    return;
     35  }
     36 
     37  VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state);
     38  if (flags.disambiguate) {
     39    // Always use unshifted codepoint
     40    if (c >= 'A' && c <= 'Z') {
     41      c += 'a' - 'A';
     42      mod |= VTERM_MOD_SHIFT;
     43    }
     44 
     45    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1);
     46    return;
     47  }
     48 
     49  if (mod & VTERM_MOD_CTRL) {
     50    // Handle special cases. These are taken from kitty, but seem mostly
     51    // consistent across terminals.
     52    switch (c) {
     53    case '2':
     54    case ' ':
     55      // Ctrl+2 is NUL to match Ctrl+@ (which is Shift+2 on US keyboards)
     56      // Ctrl+Space is also NUL for some reason
     57      c = 0x00;
     58      break;
     59    case '3':
     60    case '4':
     61    case '5':
     62    case '6':
     63    case '7':
     64      // Ctrl+3 through Ctrl+7 are sequential starting from 0x1b. Importantly,
     65      // this means that Ctrl+6 emits 0x1e (the same as Ctrl+^ on US keyboards)
     66      c = 0x1b + c - '3';
     67      break;
     68    case '8':
     69      // Ctrl+8 is DEL
     70      c = 0x7f;
     71      break;
     72    case '/':
     73      // Ctrl+/ is equivalent to Ctrl+_ for historic reasons
     74      c = 0x1f;
     75      break;
     76    default:
     77      if (c >= '@' && c <= 0x7f) {
     78        c &= 0x1f;
     79      }
     80      break;
     81    }
     82  }
     83 
     84  vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
     85 }
     86 
     87 typedef struct {
     88  enum {
     89    KEYCODE_NONE,
     90    KEYCODE_LITERAL,
     91    KEYCODE_TAB,
     92    KEYCODE_ENTER,
     93    KEYCODE_SS3,
     94    KEYCODE_CSI,
     95    KEYCODE_CSI_CURSOR,
     96    KEYCODE_CSINUM,
     97    KEYCODE_KEYPAD,
     98  } type;
     99  int literal;
    100  int csinum;
    101 } keycodes_s;
    102 
    103 static keycodes_s keycodes[] = {
    104  { KEYCODE_NONE, NUL, 0 },  // NONE
    105 
    106  { KEYCODE_ENTER,   '\r',   0 },  // ENTER
    107  { KEYCODE_TAB,     '\t',   0 },  // TAB
    108  { KEYCODE_LITERAL, '\x7f', 0 },  // BACKSPACE == ASCII DEL
    109  { KEYCODE_LITERAL, '\x1b', 0 },  // ESCAPE
    110 
    111  { KEYCODE_CSI_CURSOR, 'A', 0 },  // UP
    112  { KEYCODE_CSI_CURSOR, 'B', 0 },  // DOWN
    113  { KEYCODE_CSI_CURSOR, 'D', 0 },  // LEFT
    114  { KEYCODE_CSI_CURSOR, 'C', 0 },  // RIGHT
    115 
    116  { KEYCODE_CSINUM, '~', 2     },  // INS
    117  { KEYCODE_CSINUM, '~', 3     },  // DEL
    118  { KEYCODE_CSI_CURSOR, 'H', 0 },  // HOME
    119  { KEYCODE_CSI_CURSOR, 'F', 0 },  // END
    120  { KEYCODE_CSINUM, '~', 5     },  // PAGEUP
    121  { KEYCODE_CSINUM, '~', 6     },  // PAGEDOWN
    122 };
    123 
    124 static keycodes_s keycodes_fn[] = {
    125  { KEYCODE_NONE,   NUL,  0 },    // F0 - shouldn't happen
    126  { KEYCODE_SS3,    'P',  0 },    // F1
    127  { KEYCODE_SS3,    'Q',  0 },    // F2
    128  { KEYCODE_SS3,    'R',  0 },    // F3
    129  { KEYCODE_SS3,    'S',  0 },    // F4
    130  { KEYCODE_CSINUM, '~',  15 },   // F5
    131  { KEYCODE_CSINUM, '~',  17 },   // F6
    132  { KEYCODE_CSINUM, '~',  18 },   // F7
    133  { KEYCODE_CSINUM, '~',  19 },   // F8
    134  { KEYCODE_CSINUM, '~',  20 },   // F9
    135  { KEYCODE_CSINUM, '~',  21 },   // F10
    136  { KEYCODE_CSINUM, '~',  23 },   // F11
    137  { KEYCODE_CSINUM, '~',  24 },   // F12
    138 };
    139 
    140 static keycodes_s keycodes_kp[] = {
    141  { KEYCODE_KEYPAD, '0',  'p' },  // KP_0
    142  { KEYCODE_KEYPAD, '1',  'q' },  // KP_1
    143  { KEYCODE_KEYPAD, '2',  'r' },  // KP_2
    144  { KEYCODE_KEYPAD, '3',  's' },  // KP_3
    145  { KEYCODE_KEYPAD, '4',  't' },  // KP_4
    146  { KEYCODE_KEYPAD, '5',  'u' },  // KP_5
    147  { KEYCODE_KEYPAD, '6',  'v' },  // KP_6
    148  { KEYCODE_KEYPAD, '7',  'w' },  // KP_7
    149  { KEYCODE_KEYPAD, '8',  'x' },  // KP_8
    150  { KEYCODE_KEYPAD, '9',  'y' },  // KP_9
    151  { KEYCODE_KEYPAD, '*',  'j' },  // KP_MULT
    152  { KEYCODE_KEYPAD, '+',  'k' },  // KP_PLUS
    153  { KEYCODE_KEYPAD, ',',  'l' },  // KP_COMMA
    154  { KEYCODE_KEYPAD, '-',  'm' },  // KP_MINUS
    155  { KEYCODE_KEYPAD, '.',  'n' },  // KP_PERIOD
    156  { KEYCODE_KEYPAD, '/',  'o' },  // KP_DIVIDE
    157  { KEYCODE_KEYPAD, '\n', 'M' },  // KP_ENTER
    158  { KEYCODE_KEYPAD, '=',  'X' },  // KP_EQUAL
    159 };
    160 
    161 static keycodes_s keycodes_kp_csiu[] = {
    162  { KEYCODE_KEYPAD, 57399, 'p' },  // KP_0
    163  { KEYCODE_KEYPAD, 57400, 'q' },  // KP_1
    164  { KEYCODE_KEYPAD, 57401, 'r' },  // KP_2
    165  { KEYCODE_KEYPAD, 57402, 's' },  // KP_3
    166  { KEYCODE_KEYPAD, 57403, 't' },  // KP_4
    167  { KEYCODE_KEYPAD, 57404, 'u' },  // KP_5
    168  { KEYCODE_KEYPAD, 57405, 'v' },  // KP_6
    169  { KEYCODE_KEYPAD, 57406, 'w' },  // KP_7
    170  { KEYCODE_KEYPAD, 57407, 'x' },  // KP_8
    171  { KEYCODE_KEYPAD, 57408, 'y' },  // KP_9
    172  { KEYCODE_KEYPAD, 57411, 'j' },  // KP_MULT
    173  { KEYCODE_KEYPAD, 57413, 'k' },  // KP_PLUS
    174  { KEYCODE_KEYPAD, 57416, 'l' },  // KP_COMMA
    175  { KEYCODE_KEYPAD, 57412, 'm' },  // KP_MINUS
    176  { KEYCODE_KEYPAD, 57409, 'n' },  // KP_PERIOD
    177  { KEYCODE_KEYPAD, 57410, 'o' },  // KP_DIVIDE
    178  { KEYCODE_KEYPAD, 57414, 'M' },  // KP_ENTER
    179  { KEYCODE_KEYPAD, 57415, 'X' },  // KP_EQUAL
    180 };
    181 
    182 void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
    183 {
    184  if (key == VTERM_KEY_NONE) {
    185    return;
    186  }
    187 
    188  VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state);
    189 
    190  keycodes_s k;
    191  if (key < VTERM_KEY_FUNCTION_0) {
    192    if (key >= sizeof(keycodes)/sizeof(keycodes[0])) {
    193      return;
    194    }
    195    k = keycodes[key];
    196  } else if (key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
    197    if ((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) {
    198      return;
    199    }
    200    k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
    201  } else if (key >= VTERM_KEY_KP_0) {
    202    if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) {
    203      return;
    204    }
    205 
    206    if (flags.disambiguate) {
    207      k = keycodes_kp_csiu[key - VTERM_KEY_KP_0];
    208    } else {
    209      k = keycodes_kp[key - VTERM_KEY_KP_0];
    210    }
    211  }
    212 
    213  switch (k.type) {
    214  case KEYCODE_NONE:
    215    break;
    216 
    217  case KEYCODE_TAB:
    218    // Shift-Tab is CSI Z but plain Tab is 0x09
    219    if (flags.disambiguate) {
    220      goto case_LITERAL;
    221    } else if (mod == VTERM_MOD_SHIFT) {
    222      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
    223    } else if (mod & VTERM_MOD_SHIFT) {
    224      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1);
    225    } else {
    226      goto case_LITERAL;
    227    }
    228    break;
    229 
    230  case KEYCODE_ENTER:
    231    // Enter is CRLF in newline mode, but just LF in linefeed
    232    if (vt->state->mode.newline) {
    233      vterm_push_output_sprintf(vt, "\r\n");
    234    } else {
    235      goto case_LITERAL;
    236    }
    237    break;
    238 
    239  case KEYCODE_LITERAL:
    240                        case_LITERAL:
    241    if (flags.disambiguate) {
    242      switch (key) {
    243      case VTERM_KEY_TAB:
    244      case VTERM_KEY_ENTER:
    245      case VTERM_KEY_BACKSPACE:
    246        // If there are no mods then leave these as-is
    247        flags.disambiguate = mod != VTERM_MOD_NONE;
    248        break;
    249      default:
    250        break;
    251      }
    252    }
    253 
    254    if (flags.disambiguate) {
    255      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1);
    256    } else {
    257      vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
    258    }
    259    break;
    260 
    261  case KEYCODE_SS3:
    262                    case_SS3:
    263    if (mod == 0) {
    264      vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
    265    } else {
    266      goto case_CSI;
    267    }
    268    break;
    269 
    270  case KEYCODE_CSI:
    271                    case_CSI:
    272    if (mod == 0) {
    273      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
    274    } else {
    275      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
    276    }
    277    break;
    278 
    279  case KEYCODE_CSINUM:
    280    if (mod == 0) {
    281      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
    282    } else {
    283      vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
    284    }
    285    break;
    286 
    287  case KEYCODE_CSI_CURSOR:
    288    if (vt->state->mode.cursor) {
    289      goto case_SS3;
    290    } else {
    291      goto case_CSI;
    292    }
    293 
    294  case KEYCODE_KEYPAD:
    295    if (vt->state->mode.keypad) {
    296      k.literal = k.csinum;
    297      goto case_SS3;
    298    } else {
    299      goto case_LITERAL;
    300    }
    301  }
    302 }
    303 
    304 void vterm_keyboard_start_paste(VTerm *vt)
    305 {
    306  if (vt->state->mode.bracketpaste) {
    307    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
    308  }
    309 }
    310 
    311 void vterm_keyboard_end_paste(VTerm *vt)
    312 {
    313  if (vt->state->mode.bracketpaste) {
    314    vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
    315  }
    316 }