neovim

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

driver-ti.c (15102B)


      1 #include <errno.h>
      2 #include <stdbool.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <sys/stat.h>
      7 #include <uv.h>
      8 
      9 #include "nvim/charset.h"
     10 #include "nvim/memory.h"
     11 #include "nvim/tui/terminfo.h"
     12 #include "nvim/tui/termkey/driver-ti.h"
     13 #include "nvim/tui/termkey/termkey-internal.h"
     14 #include "nvim/tui/termkey/termkey_defs.h"
     15 
     16 #ifndef _WIN32
     17 # include <unistd.h>
     18 #else
     19 # include <io.h>
     20 #endif
     21 
     22 #include "tui/termkey/driver-ti.c.generated.h"
     23 
     24 #define streq(a, b) (!strcmp(a, b))
     25 
     26 #define MAX_FUNCNAME 9
     27 
     28 static struct {
     29  TerminfoKey ti_key;
     30  const char *funcname;
     31  TermKeyType type;
     32  TermKeySym sym;
     33  int mods;
     34 } funcs[] = {
     35  // THIS LIST MUST REMAIN SORTED!
     36  // nvim note: entries commented out without further note are not recognized by nvim.
     37 #define KDEF(x) kTermKey_##x, #x
     38  { KDEF(backspace), TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BACKSPACE, 0 },
     39  { KDEF(beg),       TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN,     0 },
     40  { KDEF(btab),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB,       TERMKEY_KEYMOD_SHIFT },
     41  // { KDEF(cancel),    TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CANCEL,    0 },
     42  { KDEF(clear),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLEAR,     0 },
     43  // { KDEF(close),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLOSE,     0 },
     44  // { KDEF(command),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COMMAND,   0 },
     45  // { KDEF(copy),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COPY,      0 },
     46  { KDEF(dc),        TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE,    0 },
     47  // { KDEF(down),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN,      0 },  // redundant, driver-csi
     48  { KDEF(end),       TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END,       0 },
     49  // { KDEF(enter),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_ENTER,     0 },
     50  // { KDEF(exit),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_EXIT,      0 },
     51  { KDEF(find),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND,      0 },
     52  // { KDEF(help),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HELP,      0 },
     53  { KDEF(home),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME,      0 },
     54  { KDEF(ic),        TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT,    0 },
     55  // { KDEF(left),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT,      0 }, // redundant: driver-csi
     56  // { KDEF(mark),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MARK,      0 },
     57  // { KDEF(message),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MESSAGE,   0 },
     58  // { KDEF(move),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MOVE,      0 },
     59  // { KDEF(next),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN,  0 },  // use "npage" right below
     60  { KDEF(npage),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN,  0 },
     61  // { KDEF(open),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPEN,      0 },
     62  // { KDEF(options),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPTIONS,   0 },
     63  { KDEF(ppage),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP,    0 },
     64  // { KDEF(previous),  TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP,    0 },  // use "ppage" right above
     65  // { KDEF(print),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PRINT,     0 },
     66  // { KDEF(redo),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REDO,      0 },
     67  // { KDEF(reference), TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFERENCE, 0 },
     68  // { KDEF(refresh),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFRESH,   0 },
     69  // { KDEF(replace),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REPLACE,   0 },
     70  // { KDEF(restart),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESTART,   0 },
     71  // { KDEF(resume),    TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESUME,    0 },
     72  // { KDEF(right),     TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT,     0 }, // redundant, driver-csi
     73  // { KDEF(save),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SAVE,      0 },
     74  { KDEF(select),    TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT,    0 },
     75  { KDEF(suspend),   TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SUSPEND,   0 },
     76  { KDEF(undo),      TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UNDO,      0 },
     77  // { KDEF(up),        TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP,        0 }, // redundant, driver-ci
     78  { 0, NULL,         0,                   0,                     0 },
     79 };
     80 
     81 // To be efficient at lookups, we store the byte sequence => keyinfo mapping
     82 // in a trie. This avoids a slow linear search through a flat list of
     83 // sequences. Because it is likely most nodes will be very sparse, we optimise
     84 // vector to store an extent map after the database is loaded.
     85 
     86 typedef enum {
     87  TYPE_KEY,
     88  TYPE_ARR,
     89 } trie_nodetype;
     90 
     91 struct trie_node {
     92  trie_nodetype type;
     93 };
     94 
     95 struct trie_node_key {
     96  trie_nodetype type;
     97  struct keyinfo key;
     98 };
     99 
    100 struct trie_node_arr {
    101  trie_nodetype type;
    102  unsigned char min, max;  // INCLUSIVE endpoints of the extent range
    103  struct trie_node *arr[];  // dynamic size at allocation time
    104 };
    105 
    106 static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node);
    107 
    108 static struct trie_node *new_node_key(TermKeyType type, TermKeySym sym, int modmask, int modset)
    109 {
    110  struct trie_node_key *n = xmalloc(sizeof(*n));
    111 
    112  n->type = TYPE_KEY;
    113 
    114  n->key.type = type;
    115  n->key.sym = sym;
    116  n->key.modifier_mask = modmask;
    117  n->key.modifier_set = modset;
    118 
    119  return (struct trie_node *)n;
    120 }
    121 
    122 static struct trie_node *new_node_arr(unsigned char min, unsigned char max)
    123 {
    124  struct trie_node_arr *n = xmalloc(sizeof(*n) + (max - min + 1) * sizeof(n->arr[0]));
    125 
    126  n->type = TYPE_ARR;
    127  n->min = min; n->max = max;
    128 
    129  int i;
    130  for (i = min; i <= max; i++) {
    131    n->arr[i - min] = NULL;
    132  }
    133 
    134  return (struct trie_node *)n;
    135 }
    136 
    137 static struct trie_node *lookup_next(struct trie_node *n, unsigned char b)
    138 {
    139  switch (n->type) {
    140  case TYPE_KEY:
    141    fprintf(stderr, "ABORT: lookup_next within a TYPE_KEY node\n");
    142    abort();
    143  case TYPE_ARR: {
    144    struct trie_node_arr *nar = (struct trie_node_arr *)n;
    145    if (b < nar->min || b > nar->max) {
    146      return NULL;
    147    }
    148    return nar->arr[b - nar->min];
    149  }
    150  }
    151 
    152  return NULL;  // Never reached but keeps compiler happy
    153 }
    154 
    155 static void free_trie(struct trie_node *n)
    156 {
    157  switch (n->type) {
    158  case TYPE_KEY:
    159    break;
    160  case TYPE_ARR: {
    161    struct trie_node_arr *nar = (struct trie_node_arr *)n;
    162    int i;
    163    for (i = nar->min; i <= nar->max; i++) {
    164      if (nar->arr[i - nar->min]) {
    165        free_trie(nar->arr[i - nar->min]);
    166      }
    167    }
    168    break;
    169  }
    170  }
    171 
    172  xfree(n);
    173 }
    174 
    175 static struct trie_node *compress_trie(struct trie_node *n)
    176 {
    177  if (!n) {
    178    return NULL;
    179  }
    180 
    181  switch (n->type) {
    182  case TYPE_KEY:
    183    return n;
    184  case TYPE_ARR: {
    185    struct trie_node_arr *nar = (struct trie_node_arr *)n;
    186    unsigned char min, max;
    187    // Find the real bounds
    188    for (min = 0; !nar->arr[min]; min++) {
    189      if (min == 255 && !nar->arr[min]) {
    190        xfree(nar);
    191        return new_node_arr(1, 0);
    192      }
    193    }
    194 
    195    for (max = 0xff; !nar->arr[max]; max--) {}
    196 
    197    struct trie_node_arr *new = (struct trie_node_arr *)new_node_arr(min, max);
    198    int i;
    199    for (i = min; i <= max; i++) {
    200      new->arr[i - min] = compress_trie(nar->arr[i]);
    201    }
    202 
    203    xfree(nar);
    204    return (struct trie_node *)new;
    205  }
    206  }
    207 
    208  return n;
    209 }
    210 
    211 static bool try_load_terminfo_key(TermKeyTI *ti, bool fn_nr, int key, bool shift, const char *name,
    212                                  struct keyinfo *info)
    213 {
    214  const char *value = NULL;
    215 
    216  if (ti->ti) {
    217    if (!fn_nr) {
    218      value = ti->ti->keys[key][shift ? 1 : 0];
    219    } else {
    220      assert(!shift);
    221      value = ti->ti->f_keys[key];
    222    }
    223  }
    224 
    225  if (ti->tk->ti_getstr_hook) {
    226    value = (ti->tk->ti_getstr_hook)(name, value, ti->tk->ti_getstr_hook_data);
    227  }
    228 
    229  if (!value || value == (char *)-1 || !value[0]) {
    230    return false;
    231  }
    232 
    233  struct trie_node *node = new_node_key(info->type, info->sym, info->modifier_mask,
    234                                        info->modifier_set);
    235  insert_seq(ti, value, node);
    236 
    237  return true;
    238 }
    239 
    240 static int load_terminfo(TermKeyTI *ti)
    241 {
    242  int i;
    243 
    244  ti->root = new_node_arr(0, 0xff);
    245  if (!ti->root) {
    246    return 0;
    247  }
    248 
    249  // First the regular key strings
    250  for (i = 0; funcs[i].funcname; i++) {
    251    char name[MAX_FUNCNAME + 5 + 1];
    252 
    253    sprintf(name, "key_%s", funcs[i].funcname);  // NOLINT(runtime/printf)
    254    if (!try_load_terminfo_key(ti, false, (int)funcs[i].ti_key, false, name, &(struct keyinfo){
    255      .type = funcs[i].type,
    256      .sym = funcs[i].sym,
    257      .modifier_mask = funcs[i].mods,
    258      .modifier_set = funcs[i].mods,
    259    })) {
    260      continue;
    261    }
    262 
    263    // Maybe it has a shifted version
    264    sprintf(name, "key_s%s", funcs[i].funcname);  // NOLINT(runtime/printf)
    265    try_load_terminfo_key(ti, false, (int)funcs[i].ti_key, true, name, &(struct keyinfo){
    266      .type = funcs[i].type,
    267      .sym = funcs[i].sym,
    268      .modifier_mask = funcs[i].mods | TERMKEY_KEYMOD_SHIFT,
    269      .modifier_set = funcs[i].mods | TERMKEY_KEYMOD_SHIFT,
    270    });
    271  }
    272 
    273  // Now the F<digit> keys
    274  for (i = 1; i <= kTerminfoFuncKeyMax; i++) {
    275    char name[9];
    276    sprintf(name, "key_f%d", i);  // NOLINT(runtime/printf)
    277    if (!try_load_terminfo_key(ti, true, (i - 1), false, name, &(struct keyinfo){
    278      .type = TERMKEY_TYPE_FUNCTION,
    279      .sym = i,
    280      .modifier_mask = 0,
    281      .modifier_set = 0,
    282    })) {
    283      break;
    284    }
    285  }
    286 
    287  // Finally mouse mode
    288  // This is overriden in nvim: we only want driver-csi mouse support
    289  if (false) {
    290    const char *value = NULL;
    291 
    292    if (ti->ti) {
    293      // value = ti->ti->keys[kTermKey_mouse][0];
    294    }
    295 
    296    if (ti->tk->ti_getstr_hook) {
    297      value = (ti->tk->ti_getstr_hook)("key_mouse", value, ti->tk->ti_getstr_hook_data);
    298    }
    299 
    300    // Some terminfos (e.g. xterm-1006) claim a different key_mouse that won't
    301    // give X10 encoding. We'll only accept this if it's exactly "\e[M"
    302    if (value && streq(value, "\x1b[M")) {
    303      struct trie_node *node = new_node_key(TERMKEY_TYPE_MOUSE, 0, 0, 0);
    304      insert_seq(ti, value, node);
    305    }
    306  }
    307 
    308  // Take copies of these terminfo strings, in case we build multiple termkey
    309  // instances for multiple different termtypes, and it's different by the
    310  // time we want to use it
    311  const char *keypad_xmit = ti->ti
    312                            ? ti->ti->defs[kTerm_keypad_xmit]
    313                            : NULL;
    314 
    315  if (keypad_xmit) {
    316    ti->start_string = xstrdup(keypad_xmit);
    317  } else {
    318    ti->start_string = NULL;
    319  }
    320 
    321  const char *keypad_local = ti->ti
    322                             ? ti->ti->defs[kTerm_keypad_local]
    323                             : NULL;
    324 
    325  if (keypad_local) {
    326    ti->stop_string = xstrdup(keypad_local);
    327  } else {
    328    ti->stop_string = NULL;
    329  }
    330 
    331  ti->root = compress_trie(ti->root);
    332 
    333  return 1;
    334 }
    335 
    336 void *new_driver_ti(TermKey *tk, TerminfoEntry *term)
    337 {
    338  TermKeyTI *ti = xmalloc(sizeof *ti);
    339 
    340  ti->tk = tk;
    341  ti->root = NULL;
    342  ti->start_string = NULL;
    343  ti->stop_string = NULL;
    344 
    345  ti->ti = term;
    346 
    347  // ti->ti may be NULL because reasons. That means the terminal wasn't
    348  // known. Lets keep going because if we get getstr hook that might invent
    349  // new strings for us
    350 
    351  return ti;
    352 }
    353 
    354 int start_driver_ti(TermKey *tk, void *info)
    355 {
    356  TermKeyTI *ti = info;
    357  struct stat statbuf;
    358  char *start_string;
    359  size_t len;
    360 
    361  if (!ti->root) {
    362    load_terminfo(ti);
    363  }
    364 
    365  start_string = ti->start_string;
    366 
    367  if (tk->fd == -1 || !start_string) {
    368    return 1;
    369  }
    370 
    371  // The terminfo database will contain keys in application cursor key mode.
    372  // We may need to enable that mode
    373 
    374  // There's no point trying to write() to a pipe
    375  if (fstat(tk->fd, &statbuf) == -1) {
    376    return 0;
    377  }
    378 
    379 #ifndef _WIN32
    380  if (S_ISFIFO(statbuf.st_mode)) {
    381    return 1;
    382  }
    383 #endif
    384 
    385  // Can't call putp or tputs because they suck and don't give us fd control
    386  len = strlen(start_string);
    387  while (len) {
    388    ssize_t result = write(tk->fd, start_string, (unsigned)len);
    389    if (result < 0) {
    390      return 0;
    391    }
    392    size_t written = (size_t)result;
    393    start_string += written;
    394    len -= written;
    395  }
    396  return 1;
    397 }
    398 
    399 int stop_driver_ti(TermKey *tk, void *info)
    400 {
    401  TermKeyTI *ti = info;
    402  struct stat statbuf;
    403  char *stop_string = ti->stop_string;
    404  size_t len;
    405 
    406  if (tk->fd == -1 || !stop_string) {
    407    return 1;
    408  }
    409 
    410  // There's no point trying to write() to a pipe
    411  if (fstat(tk->fd, &statbuf) == -1) {
    412    return 0;
    413  }
    414 
    415 #ifndef _WIN32
    416  if (S_ISFIFO(statbuf.st_mode)) {
    417    return 1;
    418  }
    419 #endif
    420 
    421  // The terminfo database will contain keys in application cursor key mode.
    422  // We may need to enable that mode
    423 
    424  // Can't call putp or tputs because they suck and don't give us fd control
    425  len = strlen(stop_string);
    426  while (len) {
    427    ssize_t result = write(tk->fd, stop_string, (unsigned)len);
    428    if (result < 0) {
    429      return 0;
    430    }
    431    size_t written = (size_t)result;
    432    stop_string += written;
    433    len -= written;
    434  }
    435  return 1;
    436 }
    437 
    438 void free_driver_ti(void *info)
    439 {
    440  TermKeyTI *ti = info;
    441 
    442  free_trie(ti->root);
    443 
    444  if (ti->start_string) {
    445    xfree(ti->start_string);
    446  }
    447 
    448  if (ti->stop_string) {
    449    xfree(ti->stop_string);
    450  }
    451 
    452  xfree(ti);
    453 }
    454 
    455 #define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
    456 
    457 TermKeyResult peekkey_ti(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep)
    458 {
    459  TermKeyTI *ti = info;
    460 
    461  if (tk->buffcount == 0) {
    462    return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE;
    463  }
    464 
    465  struct trie_node *p = ti->root;
    466 
    467  unsigned pos = 0;
    468  while (pos < tk->buffcount) {
    469    p = lookup_next(p, CHARAT(pos));
    470    if (!p) {
    471      break;
    472    }
    473 
    474    pos++;
    475 
    476    if (p->type != TYPE_KEY) {
    477      continue;
    478    }
    479 
    480    struct trie_node_key *nk = (struct trie_node_key *)p;
    481    if (nk->key.type == TERMKEY_TYPE_MOUSE) {
    482      tk->buffstart += pos;
    483      tk->buffcount -= pos;
    484 
    485      TermKeyResult mouse_result = (*tk->method.peekkey_mouse)(tk, key, nbytep);
    486 
    487      tk->buffstart -= pos;
    488      tk->buffcount += pos;
    489 
    490      if (mouse_result == TERMKEY_RES_KEY) {
    491        *nbytep += pos;
    492      }
    493 
    494      return mouse_result;
    495    }
    496 
    497    key->type = nk->key.type;
    498    key->code.sym = nk->key.sym;
    499    key->modifiers = nk->key.modifier_set;
    500    *nbytep = pos;
    501    return TERMKEY_RES_KEY;
    502  }
    503 
    504  // If p is not NULL then we hadn't walked off the end yet, so we have a
    505  // partial match
    506  if (p && !force) {
    507    return TERMKEY_RES_AGAIN;
    508  }
    509 
    510  return TERMKEY_RES_NONE;
    511 }
    512 
    513 static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node)
    514 {
    515  int pos = 0;
    516  struct trie_node *p = ti->root;
    517 
    518  // Unsigned because we'll be using it as an array subscript
    519  unsigned char b;
    520 
    521  while ((b = (unsigned char)seq[pos])) {
    522    struct trie_node *next = lookup_next(p, b);
    523    if (!next) {
    524      break;
    525    }
    526    p = next;
    527    pos++;
    528  }
    529 
    530  while ((b = (unsigned char)seq[pos])) {
    531    struct trie_node *next;
    532    if (seq[pos + 1]) {
    533      // Intermediate node
    534      next = new_node_arr(0, 0xff);
    535    } else {
    536      // Final key node
    537      next = node;
    538    }
    539 
    540    if (!next) {
    541      return 0;
    542    }
    543 
    544    switch (p->type) {
    545    case TYPE_ARR: {
    546      struct trie_node_arr *nar = (struct trie_node_arr *)p;
    547      if (b < nar->min || b > nar->max) {
    548        fprintf(stderr,
    549                "ASSERT FAIL: Trie insert at 0x%02x is outside of extent bounds (0x%02x..0x%02x)\n",
    550                b, nar->min, nar->max);
    551        abort();
    552      }
    553      nar->arr[b - nar->min] = next;
    554      p = next;
    555      break;
    556    }
    557    case TYPE_KEY:
    558      fprintf(stderr, "ASSERT FAIL: Tried to insert child node in TYPE_KEY\n");
    559      abort();
    560    }
    561 
    562    pos++;
    563  }
    564 
    565  return 1;
    566 }