Home | History | Annotate | Line # | Download | only in dist
output.c revision 1.5
      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