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