xref: /openbsd/usr.bin/less/output.c (revision 898184e3)
1 /*
2  * Copyright (C) 1984-2011  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 about less, or for information on how to
8  * contact the author, see the README file.
9  */
10 
11 
12 /*
13  * High level routines dealing with the output to the screen.
14  */
15 
16 #include "less.h"
17 #if MSDOS_COMPILER==WIN32C
18 #include "windows.h"
19 #endif
20 
21 public int errmsgs;	/* Count of messages displayed by error() */
22 public int need_clr;
23 public int final_attr;
24 public int at_prompt;
25 
26 extern volatile sig_atomic_t sigs;
27 extern int sc_width;
28 extern int so_s_width, so_e_width;
29 extern int screen_trashed;
30 extern int any_display;
31 extern int is_tty;
32 extern int oldbot;
33 
34 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
35 extern int ctldisp;
36 extern int nm_fg_color, nm_bg_color;
37 extern int bo_fg_color, bo_bg_color;
38 extern int ul_fg_color, ul_bg_color;
39 extern int so_fg_color, so_bg_color;
40 extern int bl_fg_color, bl_bg_color;
41 #endif
42 
43 /*
44  * Display the line which is in the line buffer.
45  */
46 	public void
47 put_line()
48 {
49 	register int c;
50 	register 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 	register int n;
100 	register int fd;
101 	ssize_t nwritten;
102 
103 	n = ob - obuf;
104 	if (n == 0)
105 		return;
106 
107 #if MSDOS_COMPILER==MSOFTC
108 	if (is_tty && any_display)
109 	{
110 		*ob = '\0';
111 		_outtext(obuf);
112 		ob = obuf;
113 		return;
114 	}
115 #else
116 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
117 	if (is_tty && any_display)
118 	{
119 		*ob = '\0';
120 		if (ctldisp != OPT_ONPLUS)
121 			WIN32textout(obuf, ob - obuf);
122 		else
123 		{
124 			/*
125 			 * Look for SGR escape sequences, and convert them
126 			 * to color commands.  Replace bold, underline,
127 			 * and italic escapes into colors specified via
128 			 * the -D command-line option.
129 			 */
130 			char *anchor, *p, *p_next;
131 			unsigned char fg, bg;
132 			static unsigned char at;
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 			for (anchor = p_next = obuf;
153 			     (p_next = memchr(p_next, ESC, ob - p_next)) != NULL; )
154 			{
155 				p = p_next;
156 				if (p[1] == '[')  /* "ESC-[" sequence */
157 				{
158 					if (p > anchor)
159 					{
160 						/*
161 						 * If some chars seen since
162 						 * the last escape sequence,
163 						 * write them out to the screen.
164 						 */
165 						WIN32textout(anchor, p-anchor);
166 						anchor = p;
167 					}
168 					p += 2;  /* Skip the "ESC-[" */
169 					if (is_ansi_end(*p))
170 					{
171 						/*
172 						 * Handle null escape sequence
173 						 * "ESC[m", which restores
174 						 * the normal color.
175 						 */
176 						p++;
177 						anchor = p_next = p;
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 							fg = bo_fg_color;
278 							bg = bo_bg_color;
279 					} else if (at & 2)
280 					{
281 							fg = so_fg_color;
282 							bg = so_bg_color;
283 					} else if (at & 4)
284 					{
285 							fg = ul_fg_color;
286 							bg = ul_bg_color;
287 					} else if (at & 8)
288 					{
289 							fg = bl_fg_color;
290 							bg = bl_bg_color;
291 					}
292 					fg &= 0xf;
293 					bg &= 0xf;
294 					WIN32setcolors(fg, bg);
295 					p_next = anchor = p + 1;
296 				} else
297 					p_next++;
298 			}
299 
300 			/* Output what's left in the buffer.  */
301 			WIN32textout(anchor, ob - anchor);
302 		}
303 		ob = obuf;
304 		return;
305 	}
306 #endif
307 #endif
308 	fd = (any_display) ? STDOUT_FILENO : STDERR_FILENO;
309 	nwritten = write(fd, obuf, n);
310 	if (nwritten != n) {
311 		if (nwritten == -1)
312 			quit(QUIT_ERROR);
313 		screen_trashed = 1;
314 	}
315 	ob = obuf;
316 }
317 
318 /*
319  * Output a character.
320  */
321 	public int
322 putchr(c)
323 	int c;
324 {
325 #if 0 /* fake UTF-8 output for testing */
326 	extern int utf_mode;
327 	if (utf_mode)
328 	{
329 		static char ubuf[MAX_UTF_CHAR_LEN];
330 		static int ubuf_len = 0;
331 		static int ubuf_index = 0;
332 		if (ubuf_len == 0)
333 		{
334 			ubuf_len = utf_len(c);
335 			ubuf_index = 0;
336 		}
337 		ubuf[ubuf_index++] = c;
338 		if (ubuf_index < ubuf_len)
339 			return c;
340 		c = get_wchar(ubuf) & 0xFF;
341 		ubuf_len = 0;
342 	}
343 #endif
344 	if (need_clr)
345 	{
346 		need_clr = 0;
347 		clear_bot();
348 	}
349 #if MSDOS_COMPILER
350 	if (c == '\n' && is_tty)
351 	{
352 		/* remove_top(1); */
353 		putchr('\r');
354 	}
355 #else
356 #ifdef _OSK
357 	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
358 		putchr(0x0A);
359 #endif
360 #endif
361 	/*
362 	 * Some versions of flush() write to *ob, so we must flush
363 	 * when we are still one char from the end of obuf.
364 	 */
365 	if (ob >= &obuf[sizeof(obuf)-1])
366 		flush();
367 	*ob++ = c;
368 	at_prompt = 0;
369 	return (c);
370 }
371 
372 /*
373  * Output a string.
374  */
375 	public void
376 putstr(s)
377 	register char *s;
378 {
379 	while (*s != '\0')
380 		putchr(*s++);
381 }
382 
383 
384 /*
385  * Convert an integral type to a string.
386  */
387 #define TYPE_TO_A_FUNC(funcname, type) \
388 void funcname(num, buf, len) \
389 	type num; \
390 	char *buf; \
391 	size_t len; \
392 { \
393 	int neg = (num < 0); \
394 	char tbuf[INT_STRLEN_BOUND(num)+2]; \
395 	register char *s = tbuf + sizeof(tbuf); \
396 	if (neg) num = -num; \
397 	*--s = '\0'; \
398 	do { \
399 		*--s = (num % 10) + '0'; \
400 	} while ((num /= 10) != 0); \
401 	if (neg) *--s = '-'; \
402 	strlcpy(buf, s, len); \
403 }
404 
405 TYPE_TO_A_FUNC(postoa, POSITION)
406 TYPE_TO_A_FUNC(linenumtoa, LINENUM)
407 TYPE_TO_A_FUNC(inttoa, int)
408 
409 /*
410  * Output an integer in a given radix.
411  */
412 	static int
413 iprint_int(num)
414 	int num;
415 {
416 	char buf[INT_STRLEN_BOUND(num)];
417 
418 	inttoa(num, buf, sizeof(buf));
419 	putstr(buf);
420 	return (strlen(buf));
421 }
422 
423 /*
424  * Output a line number in a given radix.
425  */
426 	static int
427 iprint_linenum(num)
428 	LINENUM num;
429 {
430 	char buf[INT_STRLEN_BOUND(num)];
431 
432 	linenumtoa(num, buf, sizeof(buf));
433 	putstr(buf);
434 	return (strlen(buf));
435 }
436 
437 /*
438  * This function implements printf-like functionality
439  * using a more portable argument list mechanism than printf's.
440  */
441 	static int
442 less_printf(fmt, parg)
443 	register char *fmt;
444 	PARG *parg;
445 {
446 	register char *s;
447 	register int col;
448 
449 	col = 0;
450 	while (*fmt != '\0')
451 	{
452 		if (*fmt != '%')
453 		{
454 			putchr(*fmt++);
455 			col++;
456 		} else
457 		{
458 			++fmt;
459 			switch (*fmt++)
460 			{
461 			case 's':
462 				s = parg->p_string;
463 				parg++;
464 				while (*s != '\0')
465 				{
466 					putchr(*s++);
467 					col++;
468 				}
469 				break;
470 			case 'd':
471 				col += iprint_int(parg->p_int);
472 				parg++;
473 				break;
474 			case 'n':
475 				col += iprint_linenum(parg->p_linenum);
476 				parg++;
477 				break;
478 			}
479 		}
480 	}
481 	return (col);
482 }
483 
484 /*
485  * Get a RETURN.
486  * If some other non-trivial char is pressed, unget it, so it will
487  * become the next command.
488  */
489 	public void
490 get_return()
491 {
492 	int c;
493 
494 #if ONLY_RETURN
495 	while ((c = getchr()) != '\n' && c != '\r')
496 		bell();
497 #else
498 	c = getchr();
499 	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
500 		ungetcc(c);
501 #endif
502 }
503 
504 /*
505  * Output a message in the lower left corner of the screen
506  * and wait for carriage return.
507  */
508 	public void
509 error(fmt, parg)
510 	char *fmt;
511 	PARG *parg;
512 {
513 	int col = 0;
514 	static char return_to_continue[] = "  (press RETURN)";
515 
516 	errmsgs++;
517 
518 	if (any_display && is_tty)
519 	{
520 		if (!oldbot)
521 			squish_check();
522 		at_exit();
523 		clear_bot();
524 		at_enter(AT_STANDOUT);
525 		col += so_s_width;
526 	}
527 
528 	col += less_printf(fmt, parg);
529 
530 	if (!(any_display && is_tty))
531 	{
532 		putchr('\n');
533 		return;
534 	}
535 
536 	putstr(return_to_continue);
537 	at_exit();
538 	col += sizeof(return_to_continue) + so_e_width;
539 
540 	get_return();
541 	lower_left();
542     clear_eol();
543 
544 	if (col >= sc_width)
545 		/*
546 		 * Printing the message has probably scrolled the screen.
547 		 * {{ Unless the terminal doesn't have auto margins,
548 		 *    in which case we just hammered on the right margin. }}
549 		 */
550 		screen_trashed = 1;
551 
552 	flush();
553 }
554 
555 static char intr_to_abort[] = "... (interrupt to abort)";
556 
557 /*
558  * Output a message in the lower left corner of the screen
559  * and don't wait for carriage return.
560  * Usually used to warn that we are beginning a potentially
561  * time-consuming operation.
562  */
563 	public void
564 ierror(fmt, parg)
565 	char *fmt;
566 	PARG *parg;
567 {
568 	at_exit();
569 	clear_bot();
570 	at_enter(AT_STANDOUT);
571 	(void) less_printf(fmt, parg);
572 	putstr(intr_to_abort);
573 	at_exit();
574 	flush();
575 	need_clr = 1;
576 }
577 
578 /*
579  * Output a message in the lower left corner of the screen
580  * and return a single-character response.
581  */
582 	public int
583 query(fmt, parg)
584 	char *fmt;
585 	PARG *parg;
586 {
587 	register int c;
588 	int col = 0;
589 
590 	if (any_display && is_tty)
591 		clear_bot();
592 
593 	(void) less_printf(fmt, parg);
594 	c = getchr();
595 
596 	if (!(any_display && is_tty))
597 	{
598 		putchr('\n');
599 		return (c);
600 	}
601 
602 	lower_left();
603 	if (col >= sc_width)
604 		screen_trashed = 1;
605 	flush();
606 
607 	return (c);
608 }
609