1/*
2 * Copyright (C) 1984-2017  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#ifndef COMMON_LVB_UNDERSCORE
19#define COMMON_LVB_UNDERSCORE 0x8000
20#endif
21#endif
22
23public int errmsgs;	/* Count of messages displayed by error() */
24public int need_clr;
25public int final_attr;
26public int at_prompt;
27
28extern int sigs;
29extern int sc_width;
30extern int so_s_width, so_e_width;
31extern int screen_trashed;
32extern int any_display;
33extern int is_tty;
34extern int oldbot;
35
36#if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
37extern int ctldisp;
38extern int nm_fg_color, nm_bg_color;
39extern int bo_fg_color, bo_bg_color;
40extern int ul_fg_color, ul_bg_color;
41extern int so_fg_color, so_bg_color;
42extern int bl_fg_color, bl_bg_color;
43extern int sgr_mode;
44#if MSDOS_COMPILER==WIN32C
45extern int have_ul;
46#endif
47#endif
48
49/*
50 * Display the line which is in the line buffer.
51 */
52	public void
53put_line()
54{
55	int c;
56	int i;
57	int a;
58
59	if (ABORT_SIGS())
60	{
61		/*
62		 * Don't output if a signal is pending.
63		 */
64		screen_trashed = 1;
65		return;
66	}
67
68	final_attr = AT_NORMAL;
69
70	for (i = 0;  (c = gline(i, &a)) != '\0';  i++)
71	{
72		at_switch(a);
73		final_attr = a;
74		if (c == '\b')
75			putbs();
76		else
77			putchr(c);
78	}
79
80	at_exit();
81}
82
83static char obuf[OUTBUF_SIZE];
84static char *ob = obuf;
85
86/*
87 * Flush buffered output.
88 *
89 * If we haven't displayed any file data yet,
90 * output messages on error output (file descriptor 2),
91 * otherwise output on standard output (file descriptor 1).
92 *
93 * This has the desirable effect of producing all
94 * error messages on error output if standard output
95 * is directed to a file.  It also does the same if
96 * we never produce any real output; for example, if
97 * the input file(s) cannot be opened.  If we do
98 * eventually produce output, code in edit() makes
99 * sure these messages can be seen before they are
100 * overwritten or scrolled away.
101 */
102	public void
103flush()
104{
105	int n;
106	int fd;
107
108	n = (int) (ob - obuf);
109	if (n == 0)
110		return;
111
112#if MSDOS_COMPILER==MSOFTC
113	if (is_tty && any_display)
114	{
115		*ob = '\0';
116		_outtext(obuf);
117		ob = obuf;
118		return;
119	}
120#else
121#if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
122	if (is_tty && any_display)
123	{
124		*ob = '\0';
125		if (ctldisp != OPT_ONPLUS)
126			WIN32textout(obuf, ob - obuf);
127		else
128		{
129			/*
130			 * Look for SGR escape sequences, and convert them
131			 * to color commands.  Replace bold, underline,
132			 * and italic escapes into colors specified via
133			 * the -D command-line option.
134			 */
135			char *anchor, *p, *p_next;
136			static int fg, fgi, bg, bgi;
137			static int at;
138			int f, b;
139#if MSDOS_COMPILER==WIN32C
140			/* Screen colors used by 3x and 4x SGR commands. */
141			static unsigned char screen_color[] = {
142				0, /* BLACK */
143				FOREGROUND_RED,
144				FOREGROUND_GREEN,
145				FOREGROUND_RED|FOREGROUND_GREEN,
146				FOREGROUND_BLUE,
147				FOREGROUND_BLUE|FOREGROUND_RED,
148				FOREGROUND_BLUE|FOREGROUND_GREEN,
149				FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED
150			};
151#else
152			static enum COLORS screen_color[] = {
153				BLACK, RED, GREEN, BROWN,
154				BLUE, MAGENTA, CYAN, LIGHTGRAY
155			};
156#endif
157
158			if (fg == 0 && bg == 0)
159			{
160				fg  = nm_fg_color & 7;
161				fgi = nm_fg_color & 8;
162				bg  = nm_bg_color & 7;
163				bgi = nm_bg_color & 8;
164			}
165			for (anchor = p_next = obuf;
166			     (p_next = memchr(p_next, ESC, ob - p_next)) != NULL; )
167			{
168				p = p_next;
169				if (p[1] == '[')  /* "ESC-[" sequence */
170				{
171					if (p > anchor)
172					{
173						/*
174						 * If some chars seen since
175						 * the last escape sequence,
176						 * write them out to the screen.
177						 */
178						WIN32textout(anchor, p-anchor);
179						anchor = p;
180					}
181					p += 2;  /* Skip the "ESC-[" */
182					if (is_ansi_end(*p))
183					{
184						/*
185						 * Handle null escape sequence
186						 * "ESC[m", which restores
187						 * the normal color.
188						 */
189						p++;
190						anchor = p_next = p;
191						fg  = nm_fg_color & 7;
192						fgi = nm_fg_color & 8;
193						bg  = nm_bg_color & 7;
194						bgi = nm_bg_color & 8;
195						at  = 0;
196						WIN32setcolors(nm_fg_color, nm_bg_color);
197						continue;
198					}
199					p_next = p;
200					at &= ~32;
201
202					/*
203					 * Select foreground/background colors
204					 * based on the escape sequence.
205					 */
206					while (!is_ansi_end(*p))
207					{
208						char *q;
209						long code = strtol(p, &q, 10);
210
211						if (*q == '\0')
212						{
213							/*
214							 * Incomplete sequence.
215							 * Leave it unprocessed
216							 * in the buffer.
217							 */
218							int slop = (int) (q - anchor);
219							/* {{ strcpy args overlap! }} */
220							strcpy(obuf, anchor);
221							ob = &obuf[slop];
222							return;
223						}
224
225						if (q == p ||
226						    code > 49 || code < 0 ||
227						    (!is_ansi_end(*q) && *q != ';'))
228						{
229							p_next = q;
230							break;
231						}
232						if (*q == ';')
233						{
234							q++;
235							at |= 32;
236						}
237
238						switch (code)
239						{
240						default:
241						/* case 0: all attrs off */
242							fg = nm_fg_color & 7;
243							bg = nm_bg_color & 7;
244							at &= 32;
245							/*
246							 * \e[0m use normal
247							 * intensities, but
248							 * \e[0;...m resets them
249							 */
250							if (at & 32)
251							{
252								fgi = 0;
253								bgi = 0;
254							} else
255							{
256								fgi = nm_fg_color & 8;
257								bgi = nm_bg_color & 8;
258							}
259							break;
260						case 1:	/* bold on */
261							fgi = 8;
262							at |= 1;
263							break;
264						case 3:	/* italic on */
265						case 7: /* inverse on */
266							at |= 2;
267							break;
268						case 4: /* underline on */
269#if MSDOS_COMPILER==WIN32C
270							if (have_ul)
271								bgi = COMMON_LVB_UNDERSCORE >> 4;
272							else
273#endif
274								bgi = 8;
275							at |= 4;
276							break;
277						case 5: /* slow blink on */
278						case 6: /* fast blink on */
279							bgi = 8;
280							at |= 8;
281							break;
282						case 8:	/* concealed on */
283							at |= 16;
284							break;
285						case 22: /* bold off */
286							fgi = 0;
287							at &= ~1;
288							break;
289						case 23: /* italic off */
290						case 27: /* inverse off */
291							at &= ~2;
292							break;
293						case 24: /* underline off */
294							bgi = 0;
295							at &= ~4;
296							break;
297						case 28: /* concealed off */
298							at &= ~16;
299							break;
300						case 30: case 31: case 32:
301						case 33: case 34: case 35:
302						case 36: case 37:
303							fg = screen_color[code - 30];
304							at |= 32;
305							break;
306						case 39: /* default fg */
307							fg = nm_fg_color & 7;
308							at |= 32;
309							break;
310						case 40: case 41: case 42:
311						case 43: case 44: case 45:
312						case 46: case 47:
313							bg = screen_color[code - 40];
314							at |= 32;
315							break;
316						case 49: /* default bg */
317							bg = nm_bg_color & 7;
318							at |= 32;
319							break;
320						}
321						p = q;
322					}
323					if (!is_ansi_end(*p) || p == p_next)
324						break;
325					/*
326					 * In SGR mode, the ANSI sequence is
327					 * always honored; otherwise if an attr
328					 * is used by itself ("\e[1m" versus
329					 * "\e[1;33m", for example), set the
330					 * color assigned to that attribute.
331					 */
332					if (sgr_mode || (at & 32))
333					{
334						if (at & 2)
335						{
336							f = bg | bgi;
337							b = fg | fgi;
338						} else
339						{
340							f = fg | fgi;
341							b = bg | bgi;
342						}
343					} else
344					{
345						if (at & 1)
346						{
347							f = bo_fg_color;
348							b = bo_bg_color;
349						} else if (at & 2)
350						{
351							f = so_fg_color;
352							b = so_bg_color;
353						} else if (at & 4)
354						{
355							f = ul_fg_color;
356							b = ul_bg_color;
357						} else if (at & 8)
358						{
359							f = bl_fg_color;
360							b = bl_bg_color;
361						} else
362						{
363							f = nm_fg_color;
364							b = nm_bg_color;
365						}
366					}
367					if (at & 16)
368						f = b ^ 8;
369					f &= 0xf;
370#if MSDOS_COMPILER==WIN32C
371					b &= 0xf | (COMMON_LVB_UNDERSCORE >> 4);
372#else
373 					b &= 0xf;
374#endif
375					WIN32setcolors(f, b);
376					p_next = anchor = p + 1;
377				} else
378					p_next++;
379			}
380
381			/* Output what's left in the buffer.  */
382			WIN32textout(anchor, ob - anchor);
383		}
384		ob = obuf;
385		return;
386	}
387#endif
388#endif
389	fd = (any_display) ? 1 : 2;
390	if (write(fd, obuf, n) != n)
391		screen_trashed = 1;
392	ob = obuf;
393}
394
395/*
396 * Output a character.
397 */
398	public int
399putchr(c)
400	int c;
401{
402#if 0 /* fake UTF-8 output for testing */
403	extern int utf_mode;
404	if (utf_mode)
405	{
406		static char ubuf[MAX_UTF_CHAR_LEN];
407		static int ubuf_len = 0;
408		static int ubuf_index = 0;
409		if (ubuf_len == 0)
410		{
411			ubuf_len = utf_len(c);
412			ubuf_index = 0;
413		}
414		ubuf[ubuf_index++] = c;
415		if (ubuf_index < ubuf_len)
416			return c;
417		c = get_wchar(ubuf) & 0xFF;
418		ubuf_len = 0;
419	}
420#endif
421	if (need_clr)
422	{
423		need_clr = 0;
424		clear_bot();
425	}
426#if MSDOS_COMPILER
427	if (c == '\n' && is_tty)
428	{
429		/* remove_top(1); */
430		putchr('\r');
431	}
432#else
433#ifdef _OSK
434	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
435		putchr(0x0A);
436#endif
437#endif
438	/*
439	 * Some versions of flush() write to *ob, so we must flush
440	 * when we are still one char from the end of obuf.
441	 */
442	if (ob >= &obuf[sizeof(obuf)-1])
443		flush();
444	*ob++ = c;
445	at_prompt = 0;
446	return (c);
447}
448
449/*
450 * Output a string.
451 */
452	public void
453putstr(s)
454	constant char *s;
455{
456	while (*s != '\0')
457		putchr(*s++);
458}
459
460
461/*
462 * Convert an integral type to a string.
463 */
464#define TYPE_TO_A_FUNC(funcname, type) \
465void funcname(num, buf) \
466	type num; \
467	char *buf; \
468{ \
469	int neg = (num < 0); \
470	char tbuf[INT_STRLEN_BOUND(num)+2]; \
471	char *s = tbuf + sizeof(tbuf); \
472	if (neg) num = -num; \
473	*--s = '\0'; \
474	do { \
475		*--s = (num % 10) + '0'; \
476	} while ((num /= 10) != 0); \
477	if (neg) *--s = '-'; \
478	strcpy(buf, s); \
479}
480
481TYPE_TO_A_FUNC(postoa, POSITION)
482TYPE_TO_A_FUNC(linenumtoa, LINENUM)
483TYPE_TO_A_FUNC(inttoa, int)
484
485/*
486 * Output an integer in a given radix.
487 */
488	static int
489iprint_int(num)
490	int num;
491{
492	char buf[INT_STRLEN_BOUND(num)];
493
494	inttoa(num, buf);
495	putstr(buf);
496	return ((int) strlen(buf));
497}
498
499/*
500 * Output a line number in a given radix.
501 */
502	static int
503iprint_linenum(num)
504	LINENUM num;
505{
506	char buf[INT_STRLEN_BOUND(num)];
507
508	linenumtoa(num, buf);
509	putstr(buf);
510	return ((int) strlen(buf));
511}
512
513/*
514 * This function implements printf-like functionality
515 * using a more portable argument list mechanism than printf's.
516 */
517	static int
518less_printf(fmt, parg)
519	char *fmt;
520	PARG *parg;
521{
522	char *s;
523	int col;
524
525	col = 0;
526	while (*fmt != '\0')
527	{
528		if (*fmt != '%')
529		{
530			putchr(*fmt++);
531			col++;
532		} else
533		{
534			++fmt;
535			switch (*fmt++)
536			{
537			case 's':
538				s = parg->p_string;
539				parg++;
540				while (*s != '\0')
541				{
542					putchr(*s++);
543					col++;
544				}
545				break;
546			case 'd':
547				col += iprint_int(parg->p_int);
548				parg++;
549				break;
550			case 'n':
551				col += iprint_linenum(parg->p_linenum);
552				parg++;
553				break;
554			case '%':
555				putchr('%');
556				break;
557			}
558		}
559	}
560	return (col);
561}
562
563/*
564 * Get a RETURN.
565 * If some other non-trivial char is pressed, unget it, so it will
566 * become the next command.
567 */
568	public void
569get_return()
570{
571	int c;
572
573#if ONLY_RETURN
574	while ((c = getchr()) != '\n' && c != '\r')
575		bell();
576#else
577	c = getchr();
578	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
579		ungetcc(c);
580#endif
581}
582
583/*
584 * Output a message in the lower left corner of the screen
585 * and wait for carriage return.
586 */
587	public void
588error(fmt, parg)
589	char *fmt;
590	PARG *parg;
591{
592	int col = 0;
593	static char return_to_continue[] = "  (press RETURN)";
594
595	errmsgs++;
596
597	if (any_display && is_tty)
598	{
599		if (!oldbot)
600			squish_check();
601		at_exit();
602		clear_bot();
603		at_enter(AT_STANDOUT);
604		col += so_s_width;
605	}
606
607	col += less_printf(fmt, parg);
608
609	if (!(any_display && is_tty))
610	{
611		putchr('\n');
612		return;
613	}
614
615	putstr(return_to_continue);
616	at_exit();
617	col += sizeof(return_to_continue) + so_e_width;
618
619	get_return();
620	lower_left();
621	clear_eol();
622
623	if (col >= sc_width)
624		/*
625		 * Printing the message has probably scrolled the screen.
626		 * {{ Unless the terminal doesn't have auto margins,
627		 *    in which case we just hammered on the right margin. }}
628		 */
629		screen_trashed = 1;
630
631	flush();
632}
633
634static char intr_to_abort[] = "... (interrupt to abort)";
635
636/*
637 * Output a message in the lower left corner of the screen
638 * and don't wait for carriage return.
639 * Usually used to warn that we are beginning a potentially
640 * time-consuming operation.
641 */
642	public void
643ierror(fmt, parg)
644	char *fmt;
645	PARG *parg;
646{
647	at_exit();
648	clear_bot();
649	at_enter(AT_STANDOUT);
650	(void) less_printf(fmt, parg);
651	putstr(intr_to_abort);
652	at_exit();
653	flush();
654	need_clr = 1;
655}
656
657/*
658 * Output a message in the lower left corner of the screen
659 * and return a single-character response.
660 */
661	public int
662query(fmt, parg)
663	char *fmt;
664	PARG *parg;
665{
666	int c;
667	int col = 0;
668
669	if (any_display && is_tty)
670		clear_bot();
671
672	(void) less_printf(fmt, parg);
673	c = getchr();
674
675	if (!(any_display && is_tty))
676	{
677		putchr('\n');
678		return (c);
679	}
680
681	lower_left();
682	if (col >= sc_width)
683		screen_trashed = 1;
684	flush();
685
686	return (c);
687}
688