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