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