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