1 /* 2 * Copyright (C) 1984-2021 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 if (need_clr) 435 { 436 need_clr = 0; 437 clear_bot(); 438 } 439 #if MSDOS_COMPILER 440 if (c == '\n' && is_tty) 441 { 442 /* remove_top(1); */ 443 putchr('\r'); 444 } 445 #else 446 #ifdef _OSK 447 if (c == '\n' && is_tty) /* In OS-9, '\n' == 0x0D */ 448 putchr(0x0A); 449 #endif 450 #endif 451 /* 452 * Some versions of flush() write to *ob, so we must flush 453 * when we are still one char from the end of obuf. 454 */ 455 if (ob >= &obuf[sizeof(obuf)-1]) 456 flush(); 457 *ob++ = c; 458 at_prompt = 0; 459 return (c); 460 } 461 462 /* 463 * Output a string. 464 */ 465 public void 466 putstr(s) 467 constant char *s; 468 { 469 while (*s != '\0') 470 putchr(*s++); 471 } 472 473 474 /* 475 * Convert an integral type to a string. 476 */ 477 #define TYPE_TO_A_FUNC(funcname, type) \ 478 void funcname(num, buf) \ 479 type num; \ 480 char *buf; \ 481 { \ 482 int neg = (num < 0); \ 483 char tbuf[INT_STRLEN_BOUND(num)+2]; \ 484 char *s = tbuf + sizeof(tbuf); \ 485 if (neg) num = -num; \ 486 *--s = '\0'; \ 487 do { \ 488 *--s = (num % 10) + '0'; \ 489 } while ((num /= 10) != 0); \ 490 if (neg) *--s = '-'; \ 491 strcpy(buf, s); \ 492 } 493 494 TYPE_TO_A_FUNC(postoa, POSITION) 495 TYPE_TO_A_FUNC(linenumtoa, LINENUM) 496 TYPE_TO_A_FUNC(inttoa, int) 497 498 /* 499 * Convert an string to an integral type. 500 */ 501 #define STR_TO_TYPE_FUNC(funcname, type) \ 502 type funcname(buf, ebuf) \ 503 char *buf; \ 504 char **ebuf; \ 505 { \ 506 type val = 0; \ 507 for (;; buf++) { \ 508 char c = *buf; \ 509 if (c < '0' || c > '9') break; \ 510 val = 10 * val + c - '0'; \ 511 } \ 512 if (ebuf != NULL) *ebuf = buf; \ 513 return val; \ 514 } 515 516 STR_TO_TYPE_FUNC(lstrtopos, POSITION) 517 STR_TO_TYPE_FUNC(lstrtoi, int) 518 519 /* 520 * Output an integer in a given radix. 521 */ 522 static int 523 iprint_int(num) 524 int num; 525 { 526 char buf[INT_STRLEN_BOUND(num)]; 527 528 inttoa(num, buf); 529 putstr(buf); 530 return ((int) strlen(buf)); 531 } 532 533 /* 534 * Output a line number in a given radix. 535 */ 536 static int 537 iprint_linenum(num) 538 LINENUM num; 539 { 540 char buf[INT_STRLEN_BOUND(num)]; 541 542 linenumtoa(num, buf); 543 putstr(buf); 544 return ((int) strlen(buf)); 545 } 546 547 /* 548 * This function implements printf-like functionality 549 * using a more portable argument list mechanism than printf's. 550 * 551 * {{ This paranoia about the portability of printf dates from experiences 552 * with systems in the 1980s and is of course no longer necessary. }} 553 */ 554 public int 555 less_printf(fmt, parg) 556 char *fmt; 557 PARG *parg; 558 { 559 char *s; 560 int col; 561 562 col = 0; 563 while (*fmt != '\0') 564 { 565 if (*fmt != '%') 566 { 567 putchr(*fmt++); 568 col++; 569 } else 570 { 571 ++fmt; 572 switch (*fmt++) 573 { 574 case 's': 575 s = parg->p_string; 576 parg++; 577 while (*s != '\0') 578 { 579 putchr(*s++); 580 col++; 581 } 582 break; 583 case 'd': 584 col += iprint_int(parg->p_int); 585 parg++; 586 break; 587 case 'n': 588 col += iprint_linenum(parg->p_linenum); 589 parg++; 590 break; 591 case 'c': 592 putchr(parg->p_char); 593 col++; 594 break; 595 case '%': 596 putchr('%'); 597 break; 598 } 599 } 600 } 601 return (col); 602 } 603 604 /* 605 * Get a RETURN. 606 * If some other non-trivial char is pressed, unget it, so it will 607 * become the next command. 608 */ 609 public void 610 get_return(VOID_PARAM) 611 { 612 int c; 613 614 #if ONLY_RETURN 615 while ((c = getchr()) != '\n' && c != '\r') 616 bell(); 617 #else 618 c = getchr(); 619 if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR) 620 ungetcc(c); 621 #endif 622 } 623 624 /* 625 * Output a message in the lower left corner of the screen 626 * and wait for carriage return. 627 */ 628 public void 629 error(fmt, parg) 630 char *fmt; 631 PARG *parg; 632 { 633 int col = 0; 634 static char return_to_continue[] = " (press RETURN)"; 635 636 errmsgs++; 637 638 if (!interactive()) 639 { 640 less_printf(fmt, parg); 641 putchr('\n'); 642 return; 643 } 644 645 if (!oldbot) 646 squish_check(); 647 at_exit(); 648 clear_bot(); 649 at_enter(AT_STANDOUT|AT_COLOR_ERROR); 650 col += so_s_width; 651 col += less_printf(fmt, parg); 652 putstr(return_to_continue); 653 at_exit(); 654 col += sizeof(return_to_continue) + so_e_width; 655 656 get_return(); 657 lower_left(); 658 clear_eol(); 659 660 if (col >= sc_width) 661 /* 662 * Printing the message has probably scrolled the screen. 663 * {{ Unless the terminal doesn't have auto margins, 664 * in which case we just hammered on the right margin. }} 665 */ 666 screen_trashed = 1; 667 668 flush(); 669 } 670 671 static char intr_to_abort[] = "... (interrupt to abort)"; 672 673 /* 674 * Output a message in the lower left corner of the screen 675 * and don't wait for carriage return. 676 * Usually used to warn that we are beginning a potentially 677 * time-consuming operation. 678 */ 679 public void 680 ierror(fmt, parg) 681 char *fmt; 682 PARG *parg; 683 { 684 at_exit(); 685 clear_bot(); 686 at_enter(AT_STANDOUT|AT_COLOR_ERROR); 687 (void) less_printf(fmt, parg); 688 putstr(intr_to_abort); 689 at_exit(); 690 flush(); 691 need_clr = 1; 692 } 693 694 /* 695 * Output a message in the lower left corner of the screen 696 * and return a single-character response. 697 */ 698 public int 699 query(fmt, parg) 700 char *fmt; 701 PARG *parg; 702 { 703 int c; 704 int col = 0; 705 706 if (interactive()) 707 clear_bot(); 708 709 (void) less_printf(fmt, parg); 710 c = getchr(); 711 712 if (interactive()) 713 { 714 lower_left(); 715 if (col >= sc_width) 716 screen_trashed = 1; 717 flush(); 718 } else 719 { 720 putchr('\n'); 721 } 722 723 if (c == 'Q') 724 quit(QUIT_OK); 725 return (c); 726 } 727