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 }