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