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