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