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