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