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