1 /* 2 * Copyright (C) 1984-2024 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * High level routines dealing with the output to the screen. 13 */ 14 15 #include "less.h" 16 #if MSDOS_COMPILER==WIN32C 17 #include "windows.h" 18 #ifndef COMMON_LVB_UNDERSCORE 19 #define COMMON_LVB_UNDERSCORE 0x8000 20 #endif 21 #endif 22 23 public int errmsgs; /* Count of messages displayed by error() */ 24 public int need_clr; 25 public int final_attr; 26 public int at_prompt; 27 28 extern int sigs; 29 extern int sc_width; 30 extern int so_s_width, so_e_width; 31 extern int is_tty; 32 extern int oldbot; 33 extern int utf_mode; 34 extern char intr_char; 35 36 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC 37 extern int ctldisp; 38 extern int nm_fg_color, nm_bg_color; 39 extern int bo_fg_color, bo_bg_color; 40 extern int ul_fg_color, ul_bg_color; 41 extern int so_fg_color, so_bg_color; 42 extern int bl_fg_color, bl_bg_color; 43 extern int sgr_mode; 44 #if MSDOS_COMPILER==WIN32C 45 extern int vt_enabled; 46 #endif 47 #endif 48 49 /* 50 * Display the line which is in the line buffer. 51 */ 52 public void put_line(void) 53 { 54 int c; 55 size_t i; 56 int a; 57 58 if (ABORT_SIGS()) 59 { 60 /* 61 * Don't output if a signal is pending. 62 */ 63 screen_trashed(); 64 return; 65 } 66 67 final_attr = AT_NORMAL; 68 69 for (i = 0; (c = gline(i, &a)) != '\0'; i++) 70 { 71 at_switch(a); 72 final_attr = a; 73 if (c == '\b') 74 putbs(); 75 else 76 putchr(c); 77 } 78 79 at_exit(); 80 } 81 82 /* 83 * win_flush has at least one non-critical issue when an escape sequence 84 * begins at the last char of the buffer, and possibly more issues. 85 * as a temporary measure to reduce likelyhood of encountering end-of-buffer 86 * issues till the SGR parser is replaced, OUTBUF_SIZE is 8K on Windows. 87 */ 88 static char obuf[OUTBUF_SIZE]; 89 static char *ob = obuf; 90 static int outfd = 2; /* stderr */ 91 92 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC 93 94 typedef unsigned t_attr; 95 96 #define A_BOLD (1u<<0) 97 #define A_ITALIC (1u<<1) 98 #define A_UNDERLINE (1u<<2) 99 #define A_BLINK (1u<<3) 100 #define A_INVERSE (1u<<4) 101 #define A_CONCEAL (1u<<5) 102 103 /* long is guaranteed 32 bits, and we reserve bits for type + RGB */ 104 typedef unsigned long t_color; 105 106 #define T_DEFAULT 0ul 107 #define T_ANSI 1ul /* colors 0-7 */ 108 109 #define CGET_ANSI(c) ((c) & 0x7) 110 111 #define C_DEFAULT (T_DEFAULT <<24) /* 0 */ 112 #define C_ANSI(c) ((T_ANSI <<24) | (c)) 113 114 /* attr/fg/bg/all 0 is the default attr/fg/bg/all, respectively */ 115 typedef struct t_sgr { 116 t_attr attr; 117 t_color fg; 118 t_color bg; 119 } t_sgr; 120 121 static constant t_sgr SGR_DEFAULT; /* = {0} */ 122 123 /* returns 0 on success, non-0 on unknown SGR code */ 124 static int update_sgr(t_sgr *sgr, long code) 125 { 126 switch (code) 127 { 128 case 0: *sgr = SGR_DEFAULT; break; 129 130 case 1: sgr->attr |= A_BOLD; break; 131 case 22: sgr->attr &= ~A_BOLD; break; 132 133 case 3: sgr->attr |= A_ITALIC; break; 134 case 23: sgr->attr &= ~A_ITALIC; break; 135 136 case 4: sgr->attr |= A_UNDERLINE; break; 137 case 24: sgr->attr &= ~A_UNDERLINE; break; 138 139 case 6: /* fast-blink, fallthrough */ 140 case 5: sgr->attr |= A_BLINK; break; 141 case 25: sgr->attr &= ~A_BLINK; break; 142 143 case 7: sgr->attr |= A_INVERSE; break; 144 case 27: sgr->attr &= ~A_INVERSE; break; 145 146 case 8: sgr->attr |= A_CONCEAL; break; 147 case 28: sgr->attr &= ~A_CONCEAL; break; 148 149 case 39: sgr->fg = C_DEFAULT; break; 150 case 49: sgr->bg = C_DEFAULT; break; 151 152 case 30: case 31: case 32: case 33: 153 case 34: case 35: case 36: case 37: 154 sgr->fg = C_ANSI(code - 30); 155 break; 156 157 case 40: case 41: case 42: case 43: 158 case 44: case 45: case 46: case 47: 159 sgr->bg = C_ANSI(code - 40); 160 break; 161 default: 162 return 1; 163 } 164 165 return 0; 166 } 167 168 static void set_win_colors(t_sgr *sgr) 169 { 170 #if MSDOS_COMPILER==WIN32C 171 /* Screen colors used by 3x and 4x SGR commands. */ 172 static unsigned char screen_color[] = { 173 0, /* BLACK */ 174 FOREGROUND_RED, 175 FOREGROUND_GREEN, 176 FOREGROUND_RED|FOREGROUND_GREEN, 177 FOREGROUND_BLUE, 178 FOREGROUND_BLUE|FOREGROUND_RED, 179 FOREGROUND_BLUE|FOREGROUND_GREEN, 180 FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED 181 }; 182 #else 183 static enum COLORS screen_color[] = { 184 BLACK, RED, GREEN, BROWN, 185 BLUE, MAGENTA, CYAN, LIGHTGRAY 186 }; 187 #endif 188 189 int fg, bg, tmp; /* Windows colors */ 190 191 /* Not "SGR mode": apply -D<x> to default fg+bg with one attribute */ 192 if (!sgr_mode && sgr->fg == C_DEFAULT && sgr->bg == C_DEFAULT) 193 { 194 switch (sgr->attr) 195 { 196 case A_BOLD: 197 WIN32setcolors(bo_fg_color, bo_bg_color); 198 return; 199 case A_UNDERLINE: 200 WIN32setcolors(ul_fg_color, ul_bg_color); 201 return; 202 case A_BLINK: 203 WIN32setcolors(bl_fg_color, bl_bg_color); 204 return; 205 case A_INVERSE: 206 WIN32setcolors(so_fg_color, so_bg_color); 207 return; 208 /* 209 * There's no -Di so italic should not be here, but to 210 * preserve legacy behavior, apply -Ds to italic too. 211 */ 212 case A_ITALIC: 213 WIN32setcolors(so_fg_color, so_bg_color); 214 return; 215 } 216 } 217 218 /* generic application of the SGR state as Windows colors */ 219 220 fg = sgr->fg == C_DEFAULT ? nm_fg_color 221 : screen_color[CGET_ANSI(sgr->fg)]; 222 223 bg = sgr->bg == C_DEFAULT ? nm_bg_color 224 : screen_color[CGET_ANSI(sgr->bg)]; 225 226 if (sgr->attr & A_BOLD) 227 fg |= 8; 228 229 if (sgr->attr & (A_BLINK | A_UNDERLINE)) 230 bg |= 8; /* TODO: can be illegible */ 231 232 if (sgr->attr & (A_INVERSE | A_ITALIC)) 233 { 234 tmp = fg; 235 fg = bg; 236 bg = tmp; 237 } 238 239 if (sgr->attr & A_CONCEAL) 240 fg = bg ^ 8; 241 242 WIN32setcolors(fg, bg); 243 } 244 245 /* like is_ansi_end, but doesn't assume c != 0 (returns 0 for c == 0) */ 246 static int is_ansi_end_0(char c) 247 { 248 return c && is_ansi_end((unsigned char)c); 249 } 250 251 static void win_flush(void) 252 { 253 if (ctldisp != OPT_ONPLUS 254 #if MSDOS_COMPILER==WIN32C 255 || (vt_enabled && sgr_mode) 256 #endif 257 ) 258 WIN32textout(obuf, ptr_diff(ob, obuf)); 259 else 260 { 261 /* 262 * Digest text, apply embedded SGR sequences as Windows-colors. 263 * By default - when -Da ("SGR mode") is unset - also apply 264 * translation of -D command-line options (at set_win_colors) 265 */ 266 char *anchor, *p, *p_next; 267 static t_sgr sgr; 268 269 /* when unsupported SGR value is encountered, like 38/48 for 270 * 256/true colors, then we abort processing this sequence, 271 * because it may expect followup values, but we don't know 272 * how many, so we've lost sync of this sequence parsing. 273 * Without VT enabled it's OK because we can't do much anyway, 274 * but with VT enabled we choose to passthrough this sequence 275 * to the terminal - which can handle it better than us. 276 * however, this means that our "sgr" var is no longer in sync 277 * with the actual terminal state, which can lead to broken 278 * colors with future sequences which we _can_ fully parse. 279 * in such case, once it happens, we keep passthrough sequences 280 * until we know we're in sync again - on a valid reset. 281 */ 282 static int sgr_bad_sync; 283 284 for (anchor = p_next = obuf; 285 (p_next = memchr(p_next, ESC, ob - p_next)) != NULL; ) 286 { 287 p = p_next; 288 if (p[1] == '[') /* "ESC-[" sequence */ 289 { 290 /* 291 * unknown SGR code ignores the rest of the seq, 292 * and allows ignoring sequences such as 293 * ^[[38;5;123m or ^[[38;2;5;6;7m 294 * (prior known codes at the same seq do apply) 295 */ 296 int bad_code = 0; 297 298 if (p > anchor) 299 { 300 /* 301 * If some chars seen since 302 * the last escape sequence, 303 * write them out to the screen. 304 */ 305 WIN32textout(anchor, ptr_diff(p, anchor)); 306 anchor = p; 307 } 308 p += 2; /* Skip the "ESC-[" */ 309 if (is_ansi_end_0(*p)) 310 { 311 /* 312 * Handle null escape sequence 313 * "ESC[m" as if it was "ESC[0m" 314 */ 315 p++; 316 anchor = p_next = p; 317 update_sgr(&sgr, 0); 318 set_win_colors(&sgr); 319 sgr_bad_sync = 0; 320 continue; 321 } 322 p_next = p; 323 324 /* 325 * Parse and apply SGR values to the SGR state 326 * based on the escape sequence. 327 */ 328 while (!is_ansi_end_0(*p)) 329 { 330 char *q; 331 long code = strtol(p, &q, 10); 332 333 if (*q == '\0') 334 { 335 /* 336 * Incomplete sequence. 337 * Leave it unprocessed 338 * in the buffer. 339 */ 340 size_t slop = ptr_diff(q, anchor); 341 memmove(obuf, anchor, slop); 342 ob = &obuf[slop]; 343 return; 344 } 345 346 if (q == p || 347 (!is_ansi_end_0(*q) && *q != ';')) 348 { 349 /* 350 * can't parse. passthrough 351 * till the end of the buffer 352 */ 353 p_next = q; 354 break; 355 } 356 if (*q == ';') 357 q++; 358 359 if (!bad_code) 360 bad_code = update_sgr(&sgr, code); 361 362 if (bad_code) 363 sgr_bad_sync = 1; 364 else if (code == 0) 365 sgr_bad_sync = 0; 366 367 p = q; 368 } 369 if (!is_ansi_end_0(*p) || p == p_next) 370 break; 371 372 if (sgr_bad_sync && vt_enabled) { 373 /* this or a prior sequence had unknown 374 * SGR value. passthrough all sequences 375 * until we're in-sync again 376 */ 377 WIN32textout(anchor, ptr_diff(p+1, anchor)); 378 } else { 379 set_win_colors(&sgr); 380 } 381 p_next = anchor = p + 1; 382 } else 383 p_next++; 384 } 385 386 /* Output what's left in the buffer. */ 387 WIN32textout(anchor, ptr_diff(ob, anchor)); 388 } 389 ob = obuf; 390 } 391 #endif 392 393 /* 394 * Flush buffered output. 395 * 396 * If we haven't displayed any file data yet, 397 * output messages on error output (file descriptor 2), 398 * otherwise output on standard output (file descriptor 1). 399 * 400 * This has the desirable effect of producing all 401 * error messages on error output if standard output 402 * is directed to a file. It also does the same if 403 * we never produce any real output; for example, if 404 * the input file(s) cannot be opened. If we do 405 * eventually produce output, code in edit() makes 406 * sure these messages can be seen before they are 407 * overwritten or scrolled away. 408 */ 409 public void flush(void) 410 { 411 size_t n; 412 413 n = ptr_diff(ob, obuf); 414 if (n == 0) 415 return; 416 ob = obuf; 417 418 #if MSDOS_COMPILER==MSOFTC 419 if (interactive()) 420 { 421 obuf[n] = '\0'; 422 _outtext(obuf); 423 return; 424 } 425 #else 426 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC 427 if (interactive()) 428 { 429 ob = obuf + n; 430 *ob = '\0'; 431 win_flush(); 432 return; 433 } 434 #endif 435 #endif 436 437 if (write(outfd, obuf, n) != n) 438 screen_trashed(); 439 } 440 441 /* 442 * Set the output file descriptor (1=stdout or 2=stderr). 443 */ 444 public void set_output(int fd) 445 { 446 flush(); 447 outfd = fd; 448 } 449 450 /* 451 * Output a character. 452 * ch is int for compatibility with tputs. 453 */ 454 public int putchr(int ch) 455 { 456 char c = (char) ch; 457 #if 0 /* fake UTF-8 output for testing */ 458 extern int utf_mode; 459 if (utf_mode) 460 { 461 static char ubuf[MAX_UTF_CHAR_LEN]; 462 static int ubuf_len = 0; 463 static int ubuf_index = 0; 464 if (ubuf_len == 0) 465 { 466 ubuf_len = utf_len(c); 467 ubuf_index = 0; 468 } 469 ubuf[ubuf_index++] = c; 470 if (ubuf_index < ubuf_len) 471 return c; 472 c = get_wchar(ubuf) & 0xFF; 473 ubuf_len = 0; 474 } 475 #endif 476 clear_bot_if_needed(); 477 #if MSDOS_COMPILER 478 if (c == '\n' && is_tty) 479 { 480 /* remove_top(1); */ 481 putchr('\r'); 482 } 483 #else 484 #ifdef _OSK 485 if (c == '\n' && is_tty) /* In OS-9, '\n' == 0x0D */ 486 putchr(0x0A); 487 #endif 488 #endif 489 /* 490 * Some versions of flush() write to *ob, so we must flush 491 * when we are still one char from the end of obuf. 492 */ 493 if (ob >= &obuf[sizeof(obuf)-1]) 494 flush(); 495 *ob++ = c; 496 at_prompt = 0; 497 return (c); 498 } 499 500 public void clear_bot_if_needed(void) 501 { 502 if (!need_clr) 503 return; 504 need_clr = 0; 505 clear_bot(); 506 } 507 508 /* 509 * Output a string. 510 */ 511 public void putstr(constant char *s) 512 { 513 while (*s != '\0') 514 putchr(*s++); 515 } 516 517 518 /* 519 * Convert an integral type to a string. 520 */ 521 #define TYPE_TO_A_FUNC(funcname, type) \ 522 void funcname(type num, char *buf, int radix) \ 523 { \ 524 int neg = (num < 0); \ 525 char tbuf[INT_STRLEN_BOUND(num)+2]; \ 526 char *s = tbuf + sizeof(tbuf); \ 527 if (neg) num = -num; \ 528 *--s = '\0'; \ 529 do { \ 530 *--s = "0123456789ABCDEF"[num % radix]; \ 531 } while ((num /= radix) != 0); \ 532 if (neg) *--s = '-'; \ 533 strcpy(buf, s); \ 534 } 535 536 TYPE_TO_A_FUNC(postoa, POSITION) 537 TYPE_TO_A_FUNC(linenumtoa, LINENUM) 538 TYPE_TO_A_FUNC(inttoa, int) 539 540 /* 541 * Convert a string to an integral type. Return ((type) -1) on overflow. 542 */ 543 #define STR_TO_TYPE_FUNC(funcname, cfuncname, type) \ 544 type cfuncname(constant char *buf, constant char **ebuf, int radix) \ 545 { \ 546 type val = 0; \ 547 lbool v = 0; \ 548 for (;; buf++) { \ 549 char c = *buf; \ 550 int digit = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; \ 551 if (digit < 0 || digit >= radix) break; \ 552 v = v || ckd_mul(&val, val, radix); \ 553 v = v || ckd_add(&val, val, digit); \ 554 } \ 555 if (ebuf != NULL) *ebuf = buf; \ 556 return v ? (type)(-1) : val; \ 557 } \ 558 type funcname(char *buf, char **ebuf, int radix) \ 559 { \ 560 constant char *cbuf = buf; \ 561 type r = cfuncname(cbuf, &cbuf, radix); \ 562 if (ebuf != NULL) *ebuf = (char *) cbuf; /*{{const-issue}}*/ \ 563 return r; \ 564 } 565 566 STR_TO_TYPE_FUNC(lstrtopos, lstrtoposc, POSITION) 567 STR_TO_TYPE_FUNC(lstrtoi, lstrtoic, int) 568 STR_TO_TYPE_FUNC(lstrtoul, lstrtoulc, unsigned long) 569 570 /* 571 * Print an integral type. 572 */ 573 #define IPRINT_FUNC(funcname, type, typetoa) \ 574 static int funcname(type num, int radix) \ 575 { \ 576 char buf[INT_STRLEN_BOUND(num)]; \ 577 typetoa(num, buf, radix); \ 578 putstr(buf); \ 579 return (int) strlen(buf); \ 580 } 581 582 IPRINT_FUNC(iprint_int, int, inttoa) 583 IPRINT_FUNC(iprint_linenum, LINENUM, linenumtoa) 584 585 /* 586 * This function implements printf-like functionality 587 * using a more portable argument list mechanism than printf's. 588 * 589 * {{ This paranoia about the portability of printf dates from experiences 590 * with systems in the 1980s and is of course no longer necessary. }} 591 */ 592 public int less_printf(constant char *fmt, PARG *parg) 593 { 594 constant char *s; 595 constant char *es; 596 int col; 597 598 col = 0; 599 while (*fmt != '\0') 600 { 601 if (*fmt != '%') 602 { 603 putchr(*fmt++); 604 col++; 605 } else 606 { 607 ++fmt; 608 switch (*fmt++) 609 { 610 case 's': 611 s = parg->p_string; 612 es = s + strlen(s); 613 parg++; 614 while (*s != '\0') 615 { 616 LWCHAR ch = step_charc(&s, +1, es); 617 constant char *ps = utf_mode ? prutfchar(ch) : prchar(ch); 618 while (*ps != '\0') 619 { 620 putchr(*ps++); 621 col++; 622 } 623 } 624 break; 625 case 'd': 626 col += iprint_int(parg->p_int, 10); 627 parg++; 628 break; 629 case 'x': 630 col += iprint_int(parg->p_int, 16); 631 parg++; 632 break; 633 case 'n': 634 col += iprint_linenum(parg->p_linenum, 10); 635 parg++; 636 break; 637 case 'c': 638 s = prchar((LWCHAR) parg->p_char); 639 parg++; 640 while (*s != '\0') 641 { 642 putchr(*s++); 643 col++; 644 } 645 break; 646 case '%': 647 putchr('%'); 648 break; 649 } 650 } 651 } 652 return (col); 653 } 654 655 /* 656 * Get a RETURN. 657 * If some other non-trivial char is pressed, unget it, so it will 658 * become the next command. 659 */ 660 public void get_return(void) 661 { 662 int c; 663 664 #if ONLY_RETURN 665 while ((c = getchr()) != '\n' && c != '\r') 666 bell(); 667 #else 668 c = getchr(); 669 if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR) 670 ungetcc((char) c); 671 #endif 672 } 673 674 /* 675 * Output a message in the lower left corner of the screen 676 * and wait for carriage return. 677 */ 678 public void error(constant char *fmt, PARG *parg) 679 { 680 int col = 0; 681 static char return_to_continue[] = " (press RETURN)"; 682 683 errmsgs++; 684 685 if (!interactive()) 686 { 687 less_printf(fmt, parg); 688 putchr('\n'); 689 return; 690 } 691 692 if (!oldbot) 693 squish_check(); 694 at_exit(); 695 clear_bot(); 696 at_enter(AT_STANDOUT|AT_COLOR_ERROR); 697 col += so_s_width; 698 col += less_printf(fmt, parg); 699 putstr(return_to_continue); 700 at_exit(); 701 col += (int) sizeof(return_to_continue) + so_e_width; 702 703 get_return(); 704 lower_left(); 705 clear_eol(); 706 707 if (col >= sc_width) 708 /* 709 * Printing the message has probably scrolled the screen. 710 * {{ Unless the terminal doesn't have auto margins, 711 * in which case we just hammered on the right margin. }} 712 */ 713 screen_trashed(); 714 715 flush(); 716 } 717 718 /* 719 * Output a message in the lower left corner of the screen 720 * and don't wait for carriage return. 721 * Usually used to warn that we are beginning a potentially 722 * time-consuming operation. 723 */ 724 static void ierror_suffix(constant char *fmt, PARG *parg, constant char *suffix1, constant char *suffix2, constant char *suffix3) 725 { 726 at_exit(); 727 clear_bot(); 728 at_enter(AT_STANDOUT|AT_COLOR_ERROR); 729 (void) less_printf(fmt, parg); 730 putstr(suffix1); 731 putstr(suffix2); 732 putstr(suffix3); 733 at_exit(); 734 flush(); 735 need_clr = 1; 736 } 737 738 public void ierror(constant char *fmt, PARG *parg) 739 { 740 ierror_suffix(fmt, parg, "... (interrupt to abort)", "", ""); 741 } 742 743 public void ixerror(constant char *fmt, PARG *parg) 744 { 745 if (!supports_ctrl_x()) 746 ierror(fmt, parg); 747 else 748 { 749 char ichar[MAX_PRCHAR_LEN+1]; 750 strcpy(ichar, prchar((LWCHAR) intr_char)); 751 ierror_suffix(fmt, parg, "... (", ichar, " or interrupt to abort)"); 752 } 753 } 754 755 /* 756 * Output a message in the lower left corner of the screen 757 * and return a single-character response. 758 */ 759 public int query(constant char *fmt, PARG *parg) 760 { 761 int c; 762 int col = 0; 763 764 if (interactive()) 765 clear_bot(); 766 767 (void) less_printf(fmt, parg); 768 c = getchr(); 769 770 if (interactive()) 771 { 772 lower_left(); 773 if (col >= sc_width) 774 screen_trashed(); 775 flush(); 776 } else 777 { 778 putchr('\n'); 779 } 780 781 if (c == 'Q') 782 quit(QUIT_OK); 783 return (c); 784 } 785