terminfo.c (19000B)
1 // Built-in fallback terminfo entries. 2 3 #include <stdbool.h> 4 #include <string.h> 5 6 #ifdef HAVE_UNIBILIUM 7 # include <unibilium.h> 8 #endif 9 10 #include "klib/kvec.h" 11 #include "nvim/api/private/defs.h" 12 #include "nvim/api/private/helpers.h" 13 #include "nvim/ascii_defs.h" 14 #include "nvim/charset.h" 15 #include "nvim/memory.h" 16 #include "nvim/tui/terminfo.h" 17 #include "nvim/tui/terminfo_builtin.h" 18 19 #ifdef __FreeBSD__ 20 # include "nvim/os/os.h" 21 #endif 22 23 typedef struct { 24 long nums[20]; 25 char *strings[20]; 26 size_t offset; 27 } TPSTACK; 28 29 #include "tui/terminfo.c.generated.h" 30 31 bool terminfo_is_term_family(const char *term, const char *family) 32 { 33 if (!term) { 34 return false; 35 } 36 size_t tlen = strlen(term); 37 size_t flen = strlen(family); 38 return tlen >= flen 39 && 0 == memcmp(term, family, flen) 40 // Per commentary in terminfo, minus is the only valid suffix separator. 41 // The screen terminfo may have a terminal name like screen.xterm. By making 42 // the dot(.) a valid separator, such terminal names will also be the 43 // terminal family of the screen. 44 && (NUL == term[flen] || '-' == term[flen] || '.' == term[flen]); 45 } 46 47 bool terminfo_is_bsd_console(const char *term) 48 { 49 #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ 50 || defined(__DragonFly__) 51 if (strequal(term, "vt220") // OpenBSD 52 || strequal(term, "vt100")) { // NetBSD 53 return true; 54 } 55 # if defined(__FreeBSD__) 56 // FreeBSD console sets TERM=xterm, but it does not support xterm features 57 // like cursor-shaping. Assume that TERM=xterm is degraded. #8644 58 return strequal(term, "xterm") && os_env_exists("XTERM_VERSION", true); 59 # endif 60 #endif 61 return false; 62 } 63 64 /// Loads a built-in terminfo db when we (unibilium) failed to load a terminfo 65 /// record from the environment (termcap systems, unrecognized $TERM, …). 66 /// We do not attempt to detect xterm pretenders here. 67 /// 68 /// @param term $TERM value 69 /// @param[out,static] termname decided builtin 'term' name 70 /// @return [allocated] terminfo structure 71 const TerminfoEntry *terminfo_from_builtin(const char *term, char **termname) 72 { 73 if (strequal(term, "ghostty") || strequal(term, "xterm-ghostty")) { 74 *termname = "ghostty"; 75 return &ghostty_terminfo; 76 } else if (terminfo_is_term_family(term, "xterm")) { 77 *termname = "xterm"; 78 return &xterm_256colour_terminfo; 79 } else if (terminfo_is_term_family(term, "screen")) { 80 *termname = "screen"; 81 return &screen_256colour_terminfo; 82 } else if (terminfo_is_term_family(term, "tmux")) { 83 *termname = "tmux"; 84 return &tmux_256colour_terminfo; 85 } else if (terminfo_is_term_family(term, "rxvt")) { 86 *termname = "rxvt"; 87 return &rxvt_256colour_terminfo; 88 } else if (terminfo_is_term_family(term, "putty")) { 89 *termname = "putty"; 90 return &putty_256colour_terminfo; 91 } else if (terminfo_is_term_family(term, "linux")) { 92 *termname = "linux"; 93 return &linux_16colour_terminfo; 94 } else if (terminfo_is_term_family(term, "interix")) { 95 *termname = "interix"; 96 return &interix_8colour_terminfo; 97 } else if (terminfo_is_term_family(term, "iterm") 98 || terminfo_is_term_family(term, "iterm2") 99 || terminfo_is_term_family(term, "iTerm.app") 100 || terminfo_is_term_family(term, "iTerm2.app")) { 101 *termname = "iterm"; 102 return &iterm_256colour_terminfo; 103 } else if (terminfo_is_term_family(term, "st")) { 104 *termname = "st"; 105 return &st_256colour_terminfo; 106 } else if (terminfo_is_term_family(term, "gnome") 107 || terminfo_is_term_family(term, "vte")) { 108 *termname = "vte"; 109 return &vte_256colour_terminfo; 110 } else if (terminfo_is_term_family(term, "cygwin")) { 111 *termname = "cygwin"; 112 return &cygwin_terminfo; 113 } else if (terminfo_is_term_family(term, "win32con")) { 114 *termname = "win32con"; 115 return &win32con_terminfo; 116 } else if (terminfo_is_term_family(term, "conemu")) { 117 *termname = "conemu"; 118 return &conemu_terminfo; 119 } else if (terminfo_is_term_family(term, "vtpcon")) { 120 *termname = "vtpcon"; 121 return &vtpcon_terminfo; 122 } else { 123 *termname = "ansi"; 124 return &ansi_terminfo; 125 } 126 } 127 128 bool terminfo_from_database(TerminfoEntry *ti, char *termname, Arena *arena) 129 { 130 #ifdef HAVE_UNIBILIUM 131 unibi_term *ut = unibi_from_term(termname); 132 if (!ut) { 133 return false; 134 } 135 136 ti->bce = unibi_get_bool(ut, unibi_back_color_erase); 137 ti->max_colors = unibi_get_num(ut, unibi_max_colors); 138 ti->lines = unibi_get_num(ut, unibi_lines); 139 ti->columns = unibi_get_num(ut, unibi_columns); 140 141 // Check for Tc or RGB 142 ti->has_Tc_or_RGB = false; 143 ti->Su = false; 144 for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { 145 const char *n = unibi_get_ext_bool_name(ut, i); 146 if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) { 147 ti->has_Tc_or_RGB = true; 148 } else if (n && !strcmp(n, "Su")) { 149 ti->Su = true; 150 } 151 } 152 153 static const enum unibi_string uni_ids[] = { 154 # define X(name) unibi_##name, 155 XLIST_TERMINFO_BUILTIN 156 # undef X 157 }; 158 159 for (size_t i = 0; i < ARRAY_SIZE(uni_ids); i++) { 160 const char *val = unibi_get_str(ut, uni_ids[i]); 161 ti->defs[i] = val ? arena_strdup(arena, val) : NULL; 162 } 163 164 static const char *uni_ext[] = { 165 # define X(informal_name, terminfo_name) #terminfo_name, 166 XLIST_TERMINFO_EXT 167 # undef X 168 }; 169 170 size_t max = unibi_count_ext_str(ut); 171 for (size_t i = 0; i < ARRAY_SIZE(uni_ext); i++) { 172 const char *name = uni_ext[i]; 173 for (size_t val = 0; val < max; val++) { 174 const char *n = unibi_get_ext_str_name(ut, val); 175 if (n && strequal(n, name)) { 176 const char *data = unibi_get_ext_str(ut, val); 177 ti->defs[kTermExtOffset + i] = data ? arena_strdup(arena, data) : NULL; 178 break; 179 } 180 } 181 } 182 183 # define X(name) { unibi_key_##name, unibi_string_begin_ }, 184 # define Y(name) { unibi_key_##name, unibi_key_s##name }, 185 static const enum unibi_string uni_keys[][2] = { 186 XYLIST_TERMINFO_KEYS 187 }; 188 # undef X 189 # undef Y 190 191 for (size_t i = 0; i < ARRAY_SIZE(uni_keys); i++) { 192 const char *val = unibi_get_str(ut, uni_keys[i][0]); 193 if (val) { 194 ti->keys[i][0] = arena_strdup(arena, val); 195 if (uni_keys[i][1] != unibi_string_begin_) { 196 const char *sval = unibi_get_str(ut, uni_keys[i][1]); 197 ti->keys[i][1] = sval ? arena_strdup(arena, sval) : NULL; 198 } 199 } 200 } 201 202 static const enum unibi_string uni_fkeys[] = { 203 # define X(name) unibi_key_##name, 204 XLIST_TERMINFO_FKEYS 205 # undef X 206 }; 207 208 for (size_t i = 0; i < ARRAY_SIZE(uni_fkeys); i++) { 209 const char *val = unibi_get_str(ut, uni_fkeys[i]); 210 ti->f_keys[i] = val ? arena_strdup(arena, val) : NULL; 211 } 212 213 unibi_destroy(ut); 214 return true; 215 #else 216 return false; 217 #endif 218 } 219 220 static const char *fmt(bool val) 221 { 222 return val ? "true" : "false"; 223 } 224 225 /// Dumps termcap info to the messages area. 226 /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). 227 /// 228 /// @return allocated string 229 String terminfo_info_msg(const TerminfoEntry *ti, const char *termname, bool from_db) 230 { 231 StringBuilder data = KV_INITIAL_VALUE; 232 233 kv_printf(data, "&term: %s\n", termname); 234 if (from_db) { 235 kv_printf(data, "using terminfo database\n"); 236 } else { 237 kv_printf(data, "using builtin terminfo\n"); 238 } 239 kv_printf(data, "\n"); 240 241 kv_printf(data, "Boolean capabilities:\n"); 242 kv_printf(data, " back_color_erase: %s\n", fmt(ti->bce)); 243 kv_printf(data, " truecolor ('Tc' or 'RGB'): %s\n", fmt(ti->has_Tc_or_RGB)); 244 kv_printf(data, " extended underline ('Su'): %s\n", fmt(ti->Su)); 245 kv_printf(data, "\n"); 246 247 kv_printf(data, "Numeric capabilities: (-1 for unknown)\n"); 248 kv_printf(data, " lines: %d\n", ti->lines); 249 kv_printf(data, " columns: %d\n", ti->columns); 250 kv_printf(data, " max_colors: %d\n", ti->columns); 251 kv_printf(data, "\n"); 252 253 kv_printf(data, "String capabilities:\n"); 254 255 static const char *string_names[] = { 256 #define X(name) #name, 257 XLIST_TERMINFO_BUILTIN 258 #undef X 259 #define X(internal_name, terminfo_name) (#internal_name " (" #terminfo_name ")"), 260 XLIST_TERMINFO_EXT 261 #undef X 262 }; 263 264 for (size_t i = 0; i < ARRAY_SIZE(string_names); i++) { 265 const char *s = ti->defs[i]; 266 if (s) { 267 kv_printf(data, " %-31s = ", string_names[i]); 268 // Most of these strings will contain escape sequences. 269 kv_transstr(&data, s, false); 270 kv_push(data, '\n'); 271 } 272 } 273 274 static const char *key_names[] = { 275 #define X(name) #name, 276 #define Y(name) #name, 277 XYLIST_TERMINFO_KEYS 278 #undef X 279 #undef Y 280 }; 281 282 for (size_t i = 0 + 1; i < ARRAY_SIZE(key_names); i++) { 283 const char *s = ti->keys[i][0]; 284 if (s) { 285 kv_printf(data, " key_%-27s = ", key_names[i]); 286 kv_transstr(&data, s, false); 287 const char *ss = ti->keys[i][1]; 288 if (ss) { 289 kv_printf(data, ", key_s%s = ", key_names[i]); 290 kv_transstr(&data, ss, false); 291 } 292 kv_push(data, '\n'); 293 } 294 } 295 296 static const char *fkey_names[] = { 297 #define X(name) #name, 298 XLIST_TERMINFO_FKEYS 299 #undef X 300 }; 301 302 for (size_t i = 0 + 1; i < ARRAY_SIZE(fkey_names); i++) { 303 const char *s = ti->f_keys[i]; 304 if (s) { 305 kv_printf(data, " key_%-27s = ", fkey_names[i]); 306 kv_transstr(&data, s, false); 307 kv_push(data, '\n'); 308 } 309 } 310 311 kv_push(data, NUL); 312 return cbuf_as_string(data.items, data.size - 1); 313 } 314 315 // The implementation of terminfo_fmt() is based on NetBSD libterminfo, 316 // with full license reproduced below 317 318 // Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc. 319 // 320 // This code is derived from software contributed to The NetBSD Foundation 321 // by Roy Marples. 322 // 323 // Redistribution and use in source and binary forms, with or without 324 // modification, are permitted provided that the following conditions 325 // are met: 326 // 1. Redistributions of source code must retain the above copyright 327 // notice, this list of conditions and the following disclaimer. 328 // 2. Redistributions in binary form must reproduce the above copyright 329 // notice, this list of conditions and the following disclaimer in the 330 // documentation and/or other materials provided with the distribution. 331 // 332 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 333 // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 334 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 335 // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 336 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 337 // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 338 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 339 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 340 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 341 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 342 343 // nvim modifications: 344 // - use typesafe param args instead of va_args and piss 345 // - caller provides the output buffer 346 // - static variables are not preserved 347 348 static int push(long num, char *string, TPSTACK *stack) 349 { 350 if (stack->offset >= ARRAY_SIZE(stack->nums)) { 351 return -1; 352 } 353 stack->nums[stack->offset] = num; 354 stack->strings[stack->offset] = string; 355 stack->offset++; 356 return 0; 357 } 358 359 static int pop(long *num, char **string, TPSTACK *stack) 360 { 361 if (stack->offset == 0) { 362 if (num) { 363 *num = 0; 364 } 365 if (string) { 366 *string = NULL; 367 } 368 return -1; 369 } 370 stack->offset--; 371 if (num) { 372 *num = stack->nums[stack->offset]; 373 } 374 if (string) { 375 *string = stack->strings[stack->offset]; 376 } 377 return 0; 378 } 379 380 static bool ochar(char **buf, const char *buf_end, int c) 381 { 382 if (c == 0) { 383 c = 0200; 384 } 385 // Check we have space and a terminator 386 if (buf_end - *buf < 2) { 387 return 0; 388 } 389 *(*buf)++ = (char)c; 390 return 1; 391 } 392 393 static bool onum(char **buf, const char *buf_end, const char *fmt, int num, size_t len) 394 { 395 const size_t LONG_STR_MAX = 21; 396 len = MAX(len, LONG_STR_MAX); 397 398 if (buf_end - *buf < (ssize_t)(len + 2)) { 399 return 0; 400 } 401 int l = snprintf(*buf, len + 2, fmt, num); 402 if (l == -1) { 403 return 0; 404 } 405 *buf += l; 406 return true; 407 } 408 409 /// @return number of chars printed or 0 for any error 410 size_t terminfo_fmt(char *buf_start, char *buf_end, const char *str, TPVAR params[9]) 411 { 412 char c, fmt[64], *fp, *ostr; 413 long val, val2; 414 long dnums[26]; // dynamic variables a-z, not preserved 415 long snums[26]; // static variables a-z, not preserved EITHER HAHA 416 memset(dnums, 0, sizeof snums); 417 memset(snums, 0, sizeof snums); 418 419 char *buf = buf_start; 420 421 size_t l, width, precision, olen; 422 TPSTACK stack; 423 unsigned done, dot, minus; 424 425 memset(&stack, 0, sizeof(stack)); 426 while ((c = *str++) != '\0') { 427 if (c != '%' || (c = *str++) == '%') { 428 if (c == '\0') { 429 break; 430 } 431 if (!ochar(&buf, buf_end, c)) { 432 return false; 433 } 434 continue; 435 } 436 437 // Handle formatting. 438 fp = fmt; 439 *fp++ = '%'; 440 done = dot = minus = 0; 441 width = precision = 0; 442 val = 0; 443 while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) { 444 switch (c) { 445 case 'c': 446 case 's': 447 *fp++ = c; 448 done = 1; 449 break; 450 case 'd': 451 case 'o': 452 case 'x': 453 case 'X': 454 *fp++ = 'l'; 455 *fp++ = c; 456 done = 1; 457 break; 458 case '#': 459 case ' ': 460 *fp++ = c; 461 break; 462 case '.': 463 *fp++ = c; 464 if (dot == 0) { 465 dot = 1; 466 width = (size_t)val; 467 } else { 468 done = 2; 469 } 470 val = 0; 471 break; 472 case ':': 473 minus = 1; 474 break; 475 case '-': 476 if (minus) { 477 *fp++ = c; 478 } else { 479 done = 1; 480 } 481 break; 482 default: 483 if (isdigit((unsigned char)c)) { 484 val = (val * 10) + (c - '0'); 485 if (val > 10000) { 486 done = 2; 487 } else { 488 *fp++ = c; 489 } 490 } else { 491 done = 1; 492 } 493 } 494 if (done == 0) { 495 c = *str++; 496 } 497 } 498 if (done == 2) { 499 // Found an error in the format 500 fp = fmt + 1; 501 *fp = *str; 502 olen = 0; 503 } else { 504 if (dot == 0) { 505 width = (size_t)val; 506 } else { 507 precision = (size_t)val; 508 } 509 olen = MAX(width, precision); 510 } 511 *fp++ = '\0'; 512 513 // Handle commands 514 switch (c) { 515 case 'c': 516 pop(&val, NULL, &stack); 517 if (!ochar(&buf, buf_end, (unsigned char)val)) { 518 return false; 519 } 520 break; 521 case 's': 522 pop(NULL, &ostr, &stack); 523 if (ostr != NULL) { 524 int r; 525 526 l = strlen(ostr); 527 if (l < olen) { 528 l = olen; 529 } 530 if ((size_t)(buf_end - buf) < (l + 1)) { 531 return false; 532 } 533 r = snprintf(buf, l + 1, 534 fmt, ostr); 535 if (r != -1) { 536 buf += (size_t)r; 537 } 538 } 539 break; 540 case 'l': 541 pop(NULL, &ostr, &stack); 542 if (ostr == NULL) { 543 l = 0; 544 } else { 545 l = strlen(ostr); 546 } 547 push((long)l, NULL, &stack); 548 break; 549 case 'd': 550 case 'o': 551 case 'x': 552 case 'X': 553 pop(&val, NULL, &stack); 554 if (onum(&buf, buf_end, fmt, (int)val, olen) == 0) { 555 return 0; 556 } 557 break; 558 case 'p': 559 if (*str < '1' || *str > '9') { 560 break; 561 } 562 l = (size_t)(*str++ - '1'); 563 if (push(params[l].num, params[l].string, &stack)) { 564 return 0; 565 } 566 break; 567 case 'P': 568 pop(&val, NULL, &stack); 569 if (*str >= 'a' && *str <= 'z') { 570 dnums[*str - 'a'] = val; 571 } else if (*str >= 'A' && *str <= 'Z') { 572 snums[*str - 'A'] = val; 573 } 574 break; 575 case 'g': 576 if (*str >= 'a' && *str <= 'z') { 577 if (push(dnums[*str - 'a'], NULL, &stack)) { 578 return 0; 579 } 580 } else if (*str >= 'A' && *str <= 'Z') { 581 if (push(snums[*str - 'A'], NULL, &stack)) { 582 return 0; 583 } 584 } 585 break; 586 case 'i': 587 params[0].num++; 588 params[1].num++; 589 break; 590 case '\'': 591 if (push((long)(unsigned char)(*str++), NULL, &stack)) { 592 return 0; 593 } 594 while (*str != '\0' && *str != '\'') { 595 str++; 596 } 597 if (*str == '\'') { 598 str++; 599 } 600 break; 601 case '{': 602 val = 0; 603 for (; isdigit((unsigned char)(*str)); str++) { 604 val = (val * 10) + (*str - '0'); 605 } 606 if (push(val, NULL, &stack)) { 607 return 0; 608 } 609 while (*str != '\0' && *str != '}') { 610 str++; 611 } 612 if (*str == '}') { 613 str++; 614 } 615 break; 616 case '+': 617 case '-': 618 case '*': 619 case '/': 620 case 'm': 621 case 'A': 622 case 'O': 623 case '&': 624 case '|': 625 case '^': 626 case '=': 627 case '<': 628 case '>': 629 pop(&val, NULL, &stack); 630 pop(&val2, NULL, &stack); 631 switch (c) { 632 case '+': 633 val = val + val2; 634 break; 635 case '-': 636 val = val2 - val; 637 break; 638 case '*': 639 val = val * val2; 640 break; 641 case '/': 642 val = val ? val2 / val : 0; 643 break; 644 case 'm': 645 val = val ? val2 % val : 0; 646 break; 647 case 'A': 648 val = val && val2; 649 break; 650 case 'O': 651 val = val || val2; 652 break; 653 case '&': 654 val = val & val2; 655 break; 656 case '|': 657 val = val | val2; 658 break; 659 case '^': 660 val = val ^ val2; 661 break; 662 case '=': 663 val = val == val2; 664 break; 665 case '<': 666 val = val2 < val; 667 break; 668 case '>': 669 val = val2 > val; 670 break; 671 } 672 if (push(val, NULL, &stack)) { 673 return 0; 674 } 675 break; 676 case '!': 677 case '~': 678 pop(&val, NULL, &stack); 679 switch (c) { 680 case '!': 681 val = !val; 682 break; 683 case '~': 684 val = ~val; 685 break; 686 } 687 if (push(val, NULL, &stack)) { 688 return 0; 689 } 690 break; 691 case '?': // if 692 break; 693 case 't': // then 694 pop(&val, NULL, &stack); 695 if (val == 0) { 696 l = 0; 697 for (; *str != '\0'; str++) { 698 if (*str != '%') { 699 continue; 700 } 701 str++; 702 if (*str == '?') { 703 l++; 704 } else if (*str == ';') { 705 if (l > 0) { 706 l--; 707 } else { 708 str++; 709 break; 710 } 711 } else if (*str == 'e' && l == 0) { 712 str++; 713 break; 714 } 715 } 716 } 717 break; 718 case 'e': // else 719 l = 0; 720 for (; *str != '\0'; str++) { 721 if (*str != '%') { 722 continue; 723 } 724 str++; 725 if (*str == '?') { 726 l++; 727 } else if (*str == ';') { 728 if (l > 0) { 729 l--; 730 } else { 731 str++; 732 break; 733 } 734 } 735 } 736 break; 737 case ';': // fi 738 break; 739 } 740 } 741 return (size_t)(buf - buf_start); 742 }