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