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