output.c revision 267654
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
20public int errmsgs;	/* Count of messages displayed by error() */
21public int need_clr;
22public int final_attr;
23public int at_prompt;
24
25extern int sigs;
26extern int sc_width;
27extern int so_s_width, so_e_width;
28extern int screen_trashed;
29extern int any_display;
30extern int is_tty;
31extern int oldbot;
32
33#if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
34extern int ctldisp;
35extern int nm_fg_color, nm_bg_color;
36extern int bo_fg_color, bo_bg_color;
37extern int ul_fg_color, ul_bg_color;
38extern int so_fg_color, so_bg_color;
39extern 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
46put_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
76static char obuf[OUTBUF_SIZE];
77static 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
96flush()
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
329putchr(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
383putstr(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) \
395void 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
411TYPE_TO_A_FUNC(postoa, POSITION)
412TYPE_TO_A_FUNC(linenumtoa, LINENUM)
413TYPE_TO_A_FUNC(inttoa, int)
414
415/*
416 * Output an integer in a given radix.
417 */
418	static int
419iprint_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
433iprint_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
448less_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
496get_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
515error(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
561static 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
570ierror(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
589query(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