xref: /freebsd/contrib/less/output.c (revision 9f23cbd6cae82fd77edfad7173432fa8dccd0a95)
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