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