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