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