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