xref: /freebsd/contrib/less/output.c (revision f0be0a1f8cefbdf5995fe43823d8d8f20f7dcd97)
1 /*
2  * Copyright (C) 1984-2009  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 int 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 
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 						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 							fg = bo_fg_color;
276 							bg = bo_bg_color;
277 					} else if (at & 2)
278 					{
279 							fg = so_fg_color;
280 							bg = so_bg_color;
281 					} else if (at & 4)
282 					{
283 							fg = ul_fg_color;
284 							bg = ul_bg_color;
285 					} else if (at & 8)
286 					{
287 							fg = bl_fg_color;
288 							bg = bl_bg_color;
289 					}
290 					fg &= 0xf;
291 					bg &= 0xf;
292 					WIN32setcolors(fg, bg);
293 					p_next = anchor = p + 1;
294 				} else
295 					p_next++;
296 			}
297 
298 			/* Output what's left in the buffer.  */
299 			WIN32textout(anchor, ob - anchor);
300 		}
301 		ob = obuf;
302 		return;
303 	}
304 #endif
305 #endif
306 	fd = (any_display) ? 1 : 2;
307 	if (write(fd, obuf, n) != n)
308 		screen_trashed = 1;
309 	ob = obuf;
310 }
311 
312 /*
313  * Output a character.
314  */
315 	public int
316 putchr(c)
317 	int c;
318 {
319 #if 0 /* fake UTF-8 output for testing */
320 	extern int utf_mode;
321 	if (utf_mode)
322 	{
323 		static char ubuf[MAX_UTF_CHAR_LEN];
324 		static int ubuf_len = 0;
325 		static int ubuf_index = 0;
326 		if (ubuf_len == 0)
327 		{
328 			ubuf_len = utf_len(c);
329 			ubuf_index = 0;
330 		}
331 		ubuf[ubuf_index++] = c;
332 		if (ubuf_index < ubuf_len)
333 			return c;
334 		c = get_wchar(ubuf) & 0xFF;
335 		ubuf_len = 0;
336 	}
337 #endif
338 	if (need_clr)
339 	{
340 		need_clr = 0;
341 		clear_bot();
342 	}
343 #if MSDOS_COMPILER
344 	if (c == '\n' && is_tty)
345 	{
346 		/* remove_top(1); */
347 		putchr('\r');
348 	}
349 #else
350 #ifdef _OSK
351 	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
352 		putchr(0x0A);
353 #endif
354 #endif
355 	/*
356 	 * Some versions of flush() write to *ob, so we must flush
357 	 * when we are still one char from the end of obuf.
358 	 */
359 	if (ob >= &obuf[sizeof(obuf)-1])
360 		flush();
361 	*ob++ = c;
362 	at_prompt = 0;
363 	return (c);
364 }
365 
366 /*
367  * Output a string.
368  */
369 	public void
370 putstr(s)
371 	register char *s;
372 {
373 	while (*s != '\0')
374 		putchr(*s++);
375 }
376 
377 
378 /*
379  * Convert an integral type to a string.
380  */
381 #define TYPE_TO_A_FUNC(funcname, type) \
382 void funcname(num, buf) \
383 	type num; \
384 	char *buf; \
385 { \
386 	int neg = (num < 0); \
387 	char tbuf[INT_STRLEN_BOUND(num)+2]; \
388 	register char *s = tbuf + sizeof(tbuf); \
389 	if (neg) num = -num; \
390 	*--s = '\0'; \
391 	do { \
392 		*--s = (num % 10) + '0'; \
393 	} while ((num /= 10) != 0); \
394 	if (neg) *--s = '-'; \
395 	strcpy(buf, s); \
396 }
397 
398 TYPE_TO_A_FUNC(postoa, POSITION)
399 TYPE_TO_A_FUNC(linenumtoa, LINENUM)
400 TYPE_TO_A_FUNC(inttoa, int)
401 
402 /*
403  * Output an integer in a given radix.
404  */
405 	static int
406 iprint_int(num)
407 	int num;
408 {
409 	char buf[INT_STRLEN_BOUND(num)];
410 
411 	inttoa(num, buf);
412 	putstr(buf);
413 	return (strlen(buf));
414 }
415 
416 /*
417  * Output a line number in a given radix.
418  */
419 	static int
420 iprint_linenum(num)
421 	LINENUM num;
422 {
423 	char buf[INT_STRLEN_BOUND(num)];
424 
425 	linenumtoa(num, buf);
426 	putstr(buf);
427 	return (strlen(buf));
428 }
429 
430 /*
431  * This function implements printf-like functionality
432  * using a more portable argument list mechanism than printf's.
433  */
434 	static int
435 less_printf(fmt, parg)
436 	register char *fmt;
437 	PARG *parg;
438 {
439 	register char *s;
440 	register int col;
441 
442 	col = 0;
443 	while (*fmt != '\0')
444 	{
445 		if (*fmt != '%')
446 		{
447 			putchr(*fmt++);
448 			col++;
449 		} else
450 		{
451 			++fmt;
452 			switch (*fmt++)
453 			{
454 			case 's':
455 				s = parg->p_string;
456 				parg++;
457 				while (*s != '\0')
458 				{
459 					putchr(*s++);
460 					col++;
461 				}
462 				break;
463 			case 'd':
464 				col += iprint_int(parg->p_int);
465 				parg++;
466 				break;
467 			case 'n':
468 				col += iprint_linenum(parg->p_linenum);
469 				parg++;
470 				break;
471 			}
472 		}
473 	}
474 	return (col);
475 }
476 
477 /*
478  * Get a RETURN.
479  * If some other non-trivial char is pressed, unget it, so it will
480  * become the next command.
481  */
482 	public void
483 get_return()
484 {
485 	int c;
486 
487 #if ONLY_RETURN
488 	while ((c = getchr()) != '\n' && c != '\r')
489 		bell();
490 #else
491 	c = getchr();
492 	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
493 		ungetcc(c);
494 #endif
495 }
496 
497 /*
498  * Output a message in the lower left corner of the screen
499  * and wait for carriage return.
500  */
501 	public void
502 error(fmt, parg)
503 	char *fmt;
504 	PARG *parg;
505 {
506 	int col = 0;
507 	static char return_to_continue[] = "  (press RETURN)";
508 
509 	errmsgs++;
510 
511 	if (any_display && is_tty)
512 	{
513 		if (!oldbot)
514 			squish_check();
515 		at_exit();
516 		clear_bot();
517 		at_enter(AT_STANDOUT);
518 		col += so_s_width;
519 	}
520 
521 	col += less_printf(fmt, parg);
522 
523 	if (!(any_display && is_tty))
524 	{
525 		putchr('\n');
526 		return;
527 	}
528 
529 	putstr(return_to_continue);
530 	at_exit();
531 	col += sizeof(return_to_continue) + so_e_width;
532 
533 	get_return();
534 	lower_left();
535 
536 	if (col >= sc_width)
537 		/*
538 		 * Printing the message has probably scrolled the screen.
539 		 * {{ Unless the terminal doesn't have auto margins,
540 		 *    in which case we just hammered on the right margin. }}
541 		 */
542 		screen_trashed = 1;
543 
544 	flush();
545 }
546 
547 static char intr_to_abort[] = "... (interrupt to abort)";
548 
549 /*
550  * Output a message in the lower left corner of the screen
551  * and don't wait for carriage return.
552  * Usually used to warn that we are beginning a potentially
553  * time-consuming operation.
554  */
555 	public void
556 ierror(fmt, parg)
557 	char *fmt;
558 	PARG *parg;
559 {
560 	at_exit();
561 	clear_bot();
562 	at_enter(AT_STANDOUT);
563 	(void) less_printf(fmt, parg);
564 	putstr(intr_to_abort);
565 	at_exit();
566 	flush();
567 	need_clr = 1;
568 }
569 
570 /*
571  * Output a message in the lower left corner of the screen
572  * and return a single-character response.
573  */
574 	public int
575 query(fmt, parg)
576 	char *fmt;
577 	PARG *parg;
578 {
579 	register int c;
580 	int col = 0;
581 
582 	if (any_display && is_tty)
583 		clear_bot();
584 
585 	(void) less_printf(fmt, parg);
586 	c = getchr();
587 
588 	if (!(any_display && is_tty))
589 	{
590 		putchr('\n');
591 		return (c);
592 	}
593 
594 	lower_left();
595 	if (col >= sc_width)
596 		screen_trashed = 1;
597 	flush();
598 
599 	return (c);
600 }
601