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