vterm_test.c (17576B)
1 #include <stdio.h> 2 #include <string.h> 3 4 #include "nvim/grid.h" 5 #include "nvim/mbyte.h" 6 #include "nvim/vterm/pen.h" 7 #include "nvim/vterm/screen.h" 8 #include "nvim/vterm/vterm_internal_defs.h" 9 #include "vterm_test.h" 10 11 int parser_text(const char bytes[], size_t len, void *user) 12 { 13 FILE *f = fopen(VTERM_TEST_FILE, "a"); 14 fprintf(f, "text "); 15 size_t i; 16 for (i = 0; i < len; i++) { 17 unsigned char b = (unsigned char)bytes[i]; 18 if (b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) { 19 break; 20 } 21 fprintf(f, i ? ",%x" : "%x", b); 22 } 23 fprintf(f, "\n"); 24 fclose(f); 25 26 return (int)i; 27 } 28 29 static void printchars(const char *s, size_t len, FILE *f) 30 { 31 while (len--) { 32 fprintf(f, "%c", (s++)[0]); 33 } 34 } 35 36 int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, 37 char command, void *user) 38 { 39 FILE *f = fopen(VTERM_TEST_FILE, "a"); 40 fprintf(f, "csi %02x", command); 41 42 if (leader && leader[0]) { 43 fprintf(f, " L="); 44 for (int i = 0; leader[i]; i++) { 45 fprintf(f, "%02x", leader[i]); 46 } 47 } 48 49 for (int i = 0; i < argcount; i++) { 50 char sep = i ? ',' : ' '; 51 52 if (args[i] == CSI_ARG_MISSING) { 53 fprintf(f, "%c*", sep); 54 } else { 55 fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : ""); 56 } 57 } 58 59 if (intermed && intermed[0]) { 60 fprintf(f, " I="); 61 for (int i = 0; intermed[i]; i++) { 62 fprintf(f, "%02x", intermed[i]); 63 } 64 } 65 66 fprintf(f, "\n"); 67 68 fclose(f); 69 70 return 1; 71 } 72 73 int parser_osc(int command, VTermStringFragment frag, void *user) 74 { 75 FILE *f = fopen(VTERM_TEST_FILE, "a"); 76 fprintf(f, "osc "); 77 78 if (frag.initial) { 79 if (command == -1) { 80 fprintf(f, "["); 81 } else { 82 fprintf(f, "[%d;", command); 83 } 84 } 85 86 printchars(frag.str, frag.len, f); 87 88 if (frag.final) { 89 fprintf(f, "]"); 90 } 91 92 fprintf(f, "\n"); 93 fclose(f); 94 95 return 1; 96 } 97 98 int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) 99 { 100 FILE *f = fopen(VTERM_TEST_FILE, "a"); 101 fprintf(f, "dcs "); 102 103 if (frag.initial) { 104 fprintf(f, "["); 105 for (size_t i = 0; i < commandlen; i++) { 106 fprintf(f, "%c", command[i]); 107 } 108 } 109 110 printchars(frag.str, frag.len, f); 111 112 if (frag.final) { 113 fprintf(f, "]"); 114 } 115 116 fprintf(f, "\n"); 117 fclose(f); 118 119 return 1; 120 } 121 122 int parser_apc(VTermStringFragment frag, void *user) 123 { 124 FILE *f = fopen(VTERM_TEST_FILE, "a"); 125 fprintf(f, "apc "); 126 127 if (frag.initial) { 128 fprintf(f, "["); 129 } 130 131 printchars(frag.str, frag.len, f); 132 133 if (frag.final) { 134 fprintf(f, "]"); 135 } 136 137 fprintf(f, "\n"); 138 fclose(f); 139 140 return 1; 141 } 142 143 int parser_pm(VTermStringFragment frag, void *user) 144 { 145 FILE *f = fopen(VTERM_TEST_FILE, "a"); 146 fprintf(f, "pm "); 147 148 if (frag.initial) { 149 fprintf(f, "["); 150 } 151 152 printchars(frag.str, frag.len, f); 153 154 if (frag.final) { 155 fprintf(f, "]"); 156 } 157 158 fprintf(f, "\n"); 159 fclose(f); 160 161 return 1; 162 } 163 164 int parser_sos(VTermStringFragment frag, void *user) 165 { 166 FILE *f = fopen(VTERM_TEST_FILE, "a"); 167 fprintf(f, "sos "); 168 169 if (frag.initial) { 170 fprintf(f, "["); 171 } 172 173 printchars(frag.str, frag.len, f); 174 175 if (frag.final) { 176 fprintf(f, "]"); 177 } 178 179 fprintf(f, "\n"); 180 fclose(f); 181 182 return 1; 183 } 184 185 int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user) 186 { 187 FILE *f = fopen(VTERM_TEST_FILE, "a"); 188 fprintf(f, "selection-set mask=%04X ", mask); 189 if (frag.initial) { 190 fprintf(f, "["); 191 } 192 printchars(frag.str, frag.len, f); 193 if (frag.final) { 194 fprintf(f, "]"); 195 } 196 fprintf(f, "\n"); 197 198 fclose(f); 199 return 1; 200 } 201 202 int selection_query(VTermSelectionMask mask, void *user) 203 { 204 FILE *f = fopen(VTERM_TEST_FILE, "a"); 205 fprintf(f, "selection-query mask=%04X\n", mask); 206 207 fclose(f); 208 return 1; 209 } 210 211 static void print_schar(FILE *f, schar_T schar) 212 { 213 char buf[MAX_SCHAR_SIZE]; 214 schar_get(buf, schar); 215 StrCharInfo ci = utf_ptr2StrCharInfo(buf); 216 bool did = false; 217 while (*ci.ptr != 0) { 218 if (did) { 219 fprintf(f, ","); 220 } 221 222 if (ci.chr.len == 1 && ci.chr.value >= 0x80) { 223 fprintf(f, "??%x", ci.chr.value); 224 } else { 225 fprintf(f, "%x", ci.chr.value); 226 } 227 did = true; 228 ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len); 229 } 230 } 231 232 bool want_state_putglyph; 233 int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) 234 { 235 if (!want_state_putglyph) { 236 return 1; 237 } 238 239 FILE *f = fopen(VTERM_TEST_FILE, "a"); 240 fprintf(f, "putglyph "); 241 print_schar(f, info->schar); 242 fprintf(f, " %d %d,%d", info->width, pos.row, pos.col); 243 if (info->protected_cell) { 244 fprintf(f, " prot"); 245 } 246 if (info->dwl) { 247 fprintf(f, " dwl"); 248 } 249 if (info->dhl) { 250 fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?"); 251 } 252 fprintf(f, "\n"); 253 254 fclose(f); 255 256 return 1; 257 } 258 259 bool want_state_movecursor; 260 VTermPos state_pos; 261 int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) 262 { 263 FILE *f = fopen(VTERM_TEST_FILE, "a"); 264 state_pos = pos; 265 266 if (want_state_movecursor) { 267 fprintf(f, "movecursor %d,%d\n", pos.row, pos.col); 268 } 269 270 fclose(f); 271 return 1; 272 } 273 274 bool want_state_scrollrect; 275 int state_scrollrect(VTermRect rect, int downward, int rightward, void *user) 276 { 277 if (!want_state_scrollrect) { 278 return 0; 279 } 280 281 FILE *f = fopen(VTERM_TEST_FILE, "a"); 282 283 fprintf(f, "scrollrect %d..%d,%d..%d => %+d,%+d\n", 284 rect.start_row, rect.end_row, rect.start_col, rect.end_col, 285 downward, rightward); 286 287 fclose(f); 288 return 1; 289 } 290 291 bool want_state_moverect; 292 int state_moverect(VTermRect dest, VTermRect src, void *user) 293 { 294 if (!want_state_moverect) { 295 return 0; 296 } 297 298 FILE *f = fopen(VTERM_TEST_FILE, "a"); 299 fprintf(f, "moverect %d..%d,%d..%d -> %d..%d,%d..%d\n", 300 src.start_row, src.end_row, src.start_col, src.end_col, 301 dest.start_row, dest.end_row, dest.start_col, dest.end_col); 302 303 fclose(f); 304 return 1; 305 } 306 307 void print_color(const VTermColor *col) 308 { 309 FILE *f = fopen(VTERM_TEST_FILE, "a"); 310 if (VTERM_COLOR_IS_RGB(col)) { 311 fprintf(f, "rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue); 312 } else if (VTERM_COLOR_IS_INDEXED(col)) { 313 fprintf(f, "idx(%d", col->indexed.idx); 314 } else { 315 fprintf(f, "invalid(%d", col->type); 316 } 317 if (VTERM_COLOR_IS_DEFAULT_FG(col)) { 318 fprintf(f, ",is_default_fg"); 319 } 320 if (VTERM_COLOR_IS_DEFAULT_BG(col)) { 321 fprintf(f, ",is_default_bg"); 322 } 323 fprintf(f, ")"); 324 fclose(f); 325 } 326 327 static VTermValueType vterm_get_prop_type(VTermProp prop) 328 { 329 switch (prop) { 330 case VTERM_PROP_CURSORVISIBLE: 331 return VTERM_VALUETYPE_BOOL; 332 case VTERM_PROP_CURSORBLINK: 333 return VTERM_VALUETYPE_BOOL; 334 case VTERM_PROP_ALTSCREEN: 335 return VTERM_VALUETYPE_BOOL; 336 case VTERM_PROP_TITLE: 337 return VTERM_VALUETYPE_STRING; 338 case VTERM_PROP_ICONNAME: 339 return VTERM_VALUETYPE_STRING; 340 case VTERM_PROP_REVERSE: 341 return VTERM_VALUETYPE_BOOL; 342 case VTERM_PROP_CURSORSHAPE: 343 return VTERM_VALUETYPE_INT; 344 case VTERM_PROP_MOUSE: 345 return VTERM_VALUETYPE_INT; 346 case VTERM_PROP_FOCUSREPORT: 347 return VTERM_VALUETYPE_BOOL; 348 case VTERM_PROP_THEMEUPDATES: 349 return VTERM_VALUETYPE_BOOL; 350 351 case VTERM_N_PROPS: 352 return 0; 353 } 354 return 0; // UNREACHABLE 355 } 356 357 bool want_state_settermprop; 358 int state_settermprop(VTermProp prop, VTermValue *val, void *user) 359 { 360 if (!want_state_settermprop) { 361 return 1; 362 } 363 364 int errcode = 0; 365 FILE *f = fopen(VTERM_TEST_FILE, "a"); 366 367 VTermValueType type = vterm_get_prop_type(prop); 368 switch (type) { 369 case VTERM_VALUETYPE_BOOL: 370 fprintf(f, "settermprop %d %s\n", prop, val->boolean ? "true" : "false"); 371 errcode = 1; 372 goto end; 373 case VTERM_VALUETYPE_INT: 374 fprintf(f, "settermprop %d %d\n", prop, val->number); 375 errcode = 1; 376 goto end; 377 case VTERM_VALUETYPE_STRING: 378 fprintf(f, "settermprop %d %s\"%.*s\"%s\n", prop, 379 val->string.initial ? "[" : "", (int)val->string.len, val->string.str, 380 val->string.final ? "]" : ""); 381 errcode = 0; 382 goto end; 383 case VTERM_VALUETYPE_COLOR: 384 fprintf(f, "settermprop %d ", prop); 385 print_color(&val->color); 386 fprintf(f, "\n"); 387 errcode = 1; 388 goto end; 389 case VTERM_N_VALUETYPES: 390 goto end; 391 } 392 393 end: 394 fclose(f); 395 return errcode; 396 } 397 398 bool want_state_erase; 399 int state_erase(VTermRect rect, int selective, void *user) 400 { 401 if (!want_state_erase) { 402 return 1; 403 } 404 405 FILE *f = fopen(VTERM_TEST_FILE, "a"); 406 407 fprintf(f, "erase %d..%d,%d..%d%s\n", 408 rect.start_row, rect.end_row, rect.start_col, rect.end_col, 409 selective ? " selective" : ""); 410 411 fclose(f); 412 return 1; 413 } 414 415 struct { 416 int bold; 417 int underline; 418 int italic; 419 int blink; 420 int reverse; 421 int conceal; 422 int strike; 423 int font; 424 int small; 425 int baseline; 426 int dim; 427 int overline; 428 VTermColor foreground; 429 VTermColor background; 430 } state_pen; 431 432 int state_setpenattr(VTermAttr attr, VTermValue *val, void *user) 433 { 434 switch (attr) { 435 case VTERM_ATTR_BOLD: 436 state_pen.bold = val->boolean; 437 break; 438 case VTERM_ATTR_UNDERLINE: 439 state_pen.underline = val->number; 440 break; 441 case VTERM_ATTR_ITALIC: 442 state_pen.italic = val->boolean; 443 break; 444 case VTERM_ATTR_BLINK: 445 state_pen.blink = val->boolean; 446 break; 447 case VTERM_ATTR_REVERSE: 448 state_pen.reverse = val->boolean; 449 break; 450 case VTERM_ATTR_CONCEAL: 451 state_pen.conceal = val->boolean; 452 break; 453 case VTERM_ATTR_STRIKE: 454 state_pen.strike = val->boolean; 455 break; 456 case VTERM_ATTR_FONT: 457 state_pen.font = val->number; 458 break; 459 case VTERM_ATTR_SMALL: 460 state_pen.small = val->boolean; 461 break; 462 case VTERM_ATTR_BASELINE: 463 state_pen.baseline = val->number; 464 break; 465 case VTERM_ATTR_DIM: 466 state_pen.dim = val->boolean; 467 break; 468 case VTERM_ATTR_OVERLINE: 469 state_pen.overline = val->boolean; 470 break; 471 case VTERM_ATTR_FOREGROUND: 472 state_pen.foreground = val->color; 473 break; 474 case VTERM_ATTR_BACKGROUND: 475 state_pen.background = val->color; 476 break; 477 478 case VTERM_N_ATTRS: 479 return 0; 480 default: 481 break; 482 } 483 484 return 1; 485 } 486 487 bool want_state_scrollback; 488 int state_sb_clear(void *user) 489 { 490 if (!want_state_scrollback) { 491 return 1; 492 } 493 494 FILE *f = fopen(VTERM_TEST_FILE, "a"); 495 fprintf(f, "sb_clear\n"); 496 fclose(f); 497 498 return 0; 499 } 500 501 bool want_screen_scrollback; 502 int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) 503 { 504 if (!want_screen_scrollback) { 505 return 1; 506 } 507 508 int eol = cols; 509 while (eol && !cells[eol - 1].schar) { 510 eol--; 511 } 512 513 FILE *f = fopen(VTERM_TEST_FILE, "a"); 514 fprintf(f, "sb_pushline %d =", cols); 515 for (int c = 0; c < eol; c++) { 516 fprintf(f, " "); 517 print_schar(f, cells[c].schar); 518 } 519 fprintf(f, "\n"); 520 521 fclose(f); 522 523 return 1; 524 } 525 526 int screen_sb_popline(int cols, VTermScreenCell *cells, void *user) 527 { 528 if (!want_screen_scrollback) { 529 return 0; 530 } 531 532 // All lines of scrollback contain "ABCDE" 533 for (int col = 0; col < cols; col++) { 534 if (col < 5) { 535 cells[col].schar = schar_from_ascii((uint32_t)('A' + col)); 536 } else { 537 cells[col].schar = 0; 538 } 539 540 cells[col].width = 1; 541 } 542 543 FILE *f = fopen(VTERM_TEST_FILE, "a"); 544 fprintf(f, "sb_popline %d\n", cols); 545 fclose(f); 546 return 1; 547 } 548 549 int screen_sb_clear(void *user) 550 { 551 if (!want_screen_scrollback) { 552 return 1; 553 } 554 555 FILE *f = fopen(VTERM_TEST_FILE, "a"); 556 fprintf(f, "sb_clear\n"); 557 fclose(f); 558 return 0; 559 } 560 561 void term_output(const char *s, size_t len, void *user) 562 { 563 FILE *f = fopen(VTERM_TEST_FILE, "a"); 564 fprintf(f, "output "); 565 for (size_t i = 0; i < len; i++) { 566 fprintf(f, "%x%s", (unsigned char)s[i], i < len - 1 ? "," : "\n"); 567 } 568 fclose(f); 569 } 570 571 int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) 572 { 573 switch (attr) { 574 case VTERM_ATTR_BOLD: 575 val->boolean = state->pen.bold; 576 return 1; 577 578 case VTERM_ATTR_UNDERLINE: 579 val->number = state->pen.underline; 580 return 1; 581 582 case VTERM_ATTR_ITALIC: 583 val->boolean = state->pen.italic; 584 return 1; 585 586 case VTERM_ATTR_BLINK: 587 val->boolean = state->pen.blink; 588 return 1; 589 590 case VTERM_ATTR_REVERSE: 591 val->boolean = state->pen.reverse; 592 return 1; 593 594 case VTERM_ATTR_CONCEAL: 595 val->boolean = state->pen.conceal; 596 return 1; 597 598 case VTERM_ATTR_STRIKE: 599 val->boolean = state->pen.strike; 600 return 1; 601 602 case VTERM_ATTR_FONT: 603 val->number = state->pen.font; 604 return 1; 605 606 case VTERM_ATTR_FOREGROUND: 607 val->color = state->pen.fg; 608 return 1; 609 610 case VTERM_ATTR_BACKGROUND: 611 val->color = state->pen.bg; 612 return 1; 613 614 case VTERM_ATTR_SMALL: 615 val->boolean = state->pen.small; 616 return 1; 617 618 case VTERM_ATTR_BASELINE: 619 val->number = state->pen.baseline; 620 return 1; 621 622 case VTERM_ATTR_URI: 623 val->number = state->pen.uri; 624 return 1; 625 626 case VTERM_ATTR_DIM: 627 val->boolean = state->pen.dim; 628 return 1; 629 630 case VTERM_ATTR_OVERLINE: 631 val->boolean = state->pen.overline; 632 return 1; 633 634 case VTERM_N_ATTRS: 635 return 0; 636 } 637 638 return 0; 639 } 640 641 static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) 642 { 643 if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) { 644 return 1; 645 } 646 if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) { 647 return 1; 648 } 649 if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) { 650 return 1; 651 } 652 if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) { 653 return 1; 654 } 655 if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) { 656 return 1; 657 } 658 if ((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) { 659 return 1; 660 } 661 if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) { 662 return 1; 663 } 664 if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) { 665 return 1; 666 } 667 if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) { 668 return 1; 669 } 670 if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) { 671 return 1; 672 } 673 if ((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) { 674 return 1; 675 } 676 if ((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) { 677 return 1; 678 } 679 if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) { 680 return 1; 681 } 682 if ((attrs & VTERM_ATTR_DIM_MASK) && (a->pen.dim != b->pen.dim)) { 683 return 1; 684 } 685 if ((attrs & VTERM_ATTR_OVERLINE_MASK) && (a->pen.overline != b->pen.overline)) { 686 return 1; 687 } 688 689 return 0; 690 } 691 692 int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, 693 VTermAttrMask attrs) 694 { 695 ScreenCell *target = getcell(screen, pos.row, pos.col); 696 697 // TODO(vterm): bounds check 698 extent->start_row = pos.row; 699 extent->end_row = pos.row + 1; 700 701 if (extent->start_col < 0) { 702 extent->start_col = 0; 703 } 704 if (extent->end_col < 0) { 705 extent->end_col = screen->cols; 706 } 707 708 int col; 709 710 for (col = pos.col - 1; col >= extent->start_col; col--) { 711 if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { 712 break; 713 } 714 } 715 extent->start_col = col + 1; 716 717 for (col = pos.col + 1; col < extent->end_col; col++) { 718 if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { 719 break; 720 } 721 } 722 extent->end_col = col - 1; 723 724 return 1; 725 } 726 727 /// Does not NUL-terminate the buffer 728 size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, 729 const VTermRect rect) 730 { 731 size_t outpos = 0; 732 int padding = 0; 733 734 #define PUT(bytes, thislen) \ 735 if (true) { \ 736 if (buffer && outpos + thislen <= len) \ 737 memcpy((char *)buffer + outpos, bytes, thislen); \ 738 outpos += thislen; \ 739 } \ 740 741 for (int row = rect.start_row; row < rect.end_row; row++) { 742 for (int col = rect.start_col; col < rect.end_col; col++) { 743 ScreenCell *cell = getcell(screen, row, col); 744 745 if (cell->schar == 0) { 746 // Erased cell, might need a space 747 padding++; 748 } else if (cell->schar == (uint32_t)-1) { 749 // Gap behind a double-width char, do nothing 750 } else { 751 while (padding) { 752 PUT(" ", 1); 753 padding--; 754 } 755 char buf[MAX_SCHAR_SIZE + 1]; 756 size_t thislen = schar_get(buf, cell->schar); 757 PUT(buf, thislen); 758 } 759 } 760 761 if (row < rect.end_row - 1) { 762 PUT("\n", 1); 763 padding = 0; 764 } 765 } 766 767 return outpos; 768 } 769 770 int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) 771 { 772 // This cell is EOL if this and every cell to the right is black 773 for (; pos.col < screen->cols; pos.col++) { 774 ScreenCell *cell = getcell(screen, pos.row, pos.col); 775 if (cell->schar != 0) { 776 return 0; 777 } 778 } 779 780 return 1; 781 } 782 783 void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) 784 { 785 *cursorpos = state->pos; 786 } 787 788 void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) 789 { 790 state->bold_is_highbright = bold_is_highbright; 791 } 792 793 /// Compares two colours. Returns true if the colors are equal, false otherwise. 794 int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) 795 { 796 // First make sure that the two colours are of the same type (RGB/Indexed) 797 if (a->type != b->type) { 798 return false; 799 } 800 801 // Depending on the type inspect the corresponding members 802 if (VTERM_COLOR_IS_INDEXED(a)) { 803 return a->indexed.idx == b->indexed.idx; 804 } else if (VTERM_COLOR_IS_RGB(a)) { 805 return (a->rgb.red == b->rgb.red) 806 && (a->rgb.green == b->rgb.green) 807 && (a->rgb.blue == b->rgb.blue); 808 } 809 810 return 0; 811 }