xref: /netbsd-src/external/bsd/less/dist/output.c (revision 838f5788460f0f133b15d706e644d692a9d4d6ec)
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  */
put_line(void)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
win_flush(void)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  */
flush(void)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  */
set_output(int fd)400 public void set_output(int fd)
401 {
402 	flush();
403 	outfd = fd;
404 }
405 
406 /*
407  * Output a character.
408  */
putchr(int c)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 
clear_bot_if_needed(void)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  */
putstr(constant char * s)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 
TYPE_TO_A_FUNC(postoa,POSITION)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  */
get_return(void)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  */
error(char * fmt,PARG * parg)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  */
ierror_suffix(char * fmt,PARG * parg,char * suffix1,char * suffix2,char * suffix3)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 
ierror(char * fmt,PARG * parg)678 public void ierror(char *fmt, PARG *parg)
679 {
680 	ierror_suffix(fmt, parg, "... (interrupt to abort)", "", "");
681 }
682 
ixerror(char * fmt,PARG * parg)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  */
query(char * fmt,PARG * parg)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