xref: /freebsd/contrib/less/output.c (revision c77c488926555ca344ae3a417544cf7a720e1de1)
1a5f0fb15SPaul Saab /*
2*c77c4889SXin LI  * Copyright (C) 1984-2024  Mark Nudelman
3a5f0fb15SPaul Saab  *
4a5f0fb15SPaul Saab  * You may distribute under the terms of either the GNU General Public
5a5f0fb15SPaul Saab  * License or the Less License, as specified in the README file.
6a5f0fb15SPaul Saab  *
796e55cc7SXin LI  * For more information, see the README file.
8a5f0fb15SPaul Saab  */
9a5f0fb15SPaul Saab 
10a5f0fb15SPaul Saab 
11a5f0fb15SPaul Saab /*
12a5f0fb15SPaul Saab  * High level routines dealing with the output to the screen.
13a5f0fb15SPaul Saab  */
14a5f0fb15SPaul Saab 
15a5f0fb15SPaul Saab #include "less.h"
16a5f0fb15SPaul Saab #if MSDOS_COMPILER==WIN32C
17a5f0fb15SPaul Saab #include "windows.h"
18b2ea2440SXin LI #ifndef COMMON_LVB_UNDERSCORE
19b2ea2440SXin LI #define COMMON_LVB_UNDERSCORE 0x8000
20b2ea2440SXin LI #endif
21a5f0fb15SPaul Saab #endif
22a5f0fb15SPaul Saab 
23a5f0fb15SPaul Saab public int errmsgs;    /* Count of messages displayed by error() */
24a5f0fb15SPaul Saab public int need_clr;
25a5f0fb15SPaul Saab public int final_attr;
267f074f9cSXin LI public int at_prompt;
27a5f0fb15SPaul Saab 
28a5f0fb15SPaul Saab extern int sigs;
29a5f0fb15SPaul Saab extern int sc_width;
30a5f0fb15SPaul Saab extern int so_s_width, so_e_width;
31a5f0fb15SPaul Saab extern int is_tty;
327f074f9cSXin LI extern int oldbot;
33*c77c4889SXin LI extern int utf_mode;
34d713e089SXin LI extern char intr_char;
35a5f0fb15SPaul Saab 
367374caaaSXin LI #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
37000ba3e8STim J. Robbins extern int ctldisp;
38000ba3e8STim J. Robbins extern int nm_fg_color, nm_bg_color;
39000ba3e8STim J. Robbins extern int bo_fg_color, bo_bg_color;
40000ba3e8STim J. Robbins extern int ul_fg_color, ul_bg_color;
41000ba3e8STim J. Robbins extern int so_fg_color, so_bg_color;
42000ba3e8STim J. Robbins extern int bl_fg_color, bl_bg_color;
43f6b74a7dSXin LI extern int sgr_mode;
44b2ea2440SXin LI #if MSDOS_COMPILER==WIN32C
452235c7feSXin LI extern int vt_enabled;
46b2ea2440SXin LI #endif
47000ba3e8STim J. Robbins #endif
48000ba3e8STim J. Robbins 
49a5f0fb15SPaul Saab /*
50a5f0fb15SPaul Saab  * Display the line which is in the line buffer.
51a5f0fb15SPaul Saab  */
52d713e089SXin LI public void put_line(void)
53a5f0fb15SPaul Saab {
541ea31627SRobert Watson 	int c;
55*c77c4889SXin LI 	size_t i;
56a5f0fb15SPaul Saab 	int a;
57a5f0fb15SPaul Saab 
58a5f0fb15SPaul Saab 	if (ABORT_SIGS())
59a5f0fb15SPaul Saab 	{
60a5f0fb15SPaul Saab 		/*
61a5f0fb15SPaul Saab 		 * Don't output if a signal is pending.
62a5f0fb15SPaul Saab 		 */
63*c77c4889SXin LI 		screen_trashed();
64a5f0fb15SPaul Saab 		return;
65a5f0fb15SPaul Saab 	}
66a5f0fb15SPaul Saab 
676dcb072bSXin LI 	final_attr = AT_NORMAL;
68a5f0fb15SPaul Saab 
69a5f0fb15SPaul Saab 	for (i = 0;  (c = gline(i, &a)) != '\0';  i++)
70a5f0fb15SPaul Saab 	{
716dcb072bSXin LI 		at_switch(a);
726dcb072bSXin LI 		final_attr = a;
73a5f0fb15SPaul Saab 		if (c == '\b')
74a5f0fb15SPaul Saab 			putbs();
75a5f0fb15SPaul Saab 		else
76a5f0fb15SPaul Saab 			putchr(c);
77a5f0fb15SPaul Saab 	}
78a5f0fb15SPaul Saab 
796dcb072bSXin LI 	at_exit();
80a5f0fb15SPaul Saab }
81a5f0fb15SPaul Saab 
82*c77c4889SXin LI /*
83*c77c4889SXin LI  * win_flush has at least one non-critical issue when an escape sequence
84*c77c4889SXin LI  * begins at the last char of the buffer, and possibly more issues.
85*c77c4889SXin LI  * as a temporary measure to reduce likelyhood of encountering end-of-buffer
86*c77c4889SXin LI  * issues till the SGR parser is replaced, OUTBUF_SIZE is 8K on Windows.
87*c77c4889SXin LI  */
88a5f0fb15SPaul Saab static char obuf[OUTBUF_SIZE];
89a5f0fb15SPaul Saab static char *ob = obuf;
902235c7feSXin LI static int outfd = 2; /* stderr */
91a5f0fb15SPaul Saab 
927374caaaSXin LI #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
93*c77c4889SXin LI 
94*c77c4889SXin LI typedef unsigned t_attr;
95*c77c4889SXin LI 
96*c77c4889SXin LI #define A_BOLD      (1u<<0)
97*c77c4889SXin LI #define A_ITALIC    (1u<<1)
98*c77c4889SXin LI #define A_UNDERLINE (1u<<2)
99*c77c4889SXin LI #define A_BLINK     (1u<<3)
100*c77c4889SXin LI #define A_INVERSE   (1u<<4)
101*c77c4889SXin LI #define A_CONCEAL   (1u<<5)
102*c77c4889SXin LI 
103*c77c4889SXin LI /* long is guaranteed 32 bits, and we reserve bits for type + RGB */
104*c77c4889SXin LI typedef unsigned long t_color;
105*c77c4889SXin LI 
106*c77c4889SXin LI #define T_DEFAULT   0ul
107*c77c4889SXin LI #define T_ANSI      1ul  /* colors 0-7 */
108*c77c4889SXin LI 
109*c77c4889SXin LI #define CGET_ANSI(c) ((c) & 0x7)
110*c77c4889SXin LI 
111*c77c4889SXin LI #define C_DEFAULT    (T_DEFAULT <<24) /* 0 */
112*c77c4889SXin LI #define C_ANSI(c)   ((T_ANSI    <<24) | (c))
113*c77c4889SXin LI 
114*c77c4889SXin LI /* attr/fg/bg/all 0 is the default attr/fg/bg/all, respectively */
115*c77c4889SXin LI typedef struct t_sgr {
116*c77c4889SXin LI 	t_attr attr;
117*c77c4889SXin LI 	t_color fg;
118*c77c4889SXin LI 	t_color bg;
119*c77c4889SXin LI } t_sgr;
120*c77c4889SXin LI 
121*c77c4889SXin LI static constant t_sgr SGR_DEFAULT; /* = {0} */
122*c77c4889SXin LI 
123*c77c4889SXin LI /* returns 0 on success, non-0 on unknown SGR code */
124*c77c4889SXin LI static int update_sgr(t_sgr *sgr, long code)
125a5f0fb15SPaul Saab {
126*c77c4889SXin LI 	switch (code)
127000ba3e8STim J. Robbins 	{
128*c77c4889SXin LI 	case  0: *sgr = SGR_DEFAULT; break;
129*c77c4889SXin LI 
130*c77c4889SXin LI 	case  1: sgr->attr |=  A_BOLD; break;
131*c77c4889SXin LI 	case 22: sgr->attr &= ~A_BOLD; break;
132*c77c4889SXin LI 
133*c77c4889SXin LI 	case  3: sgr->attr |=  A_ITALIC; break;
134*c77c4889SXin LI 	case 23: sgr->attr &= ~A_ITALIC; break;
135*c77c4889SXin LI 
136*c77c4889SXin LI 	case  4: sgr->attr |=  A_UNDERLINE; break;
137*c77c4889SXin LI 	case 24: sgr->attr &= ~A_UNDERLINE; break;
138*c77c4889SXin LI 
139*c77c4889SXin LI 	case  6: /* fast-blink, fallthrough */
140*c77c4889SXin LI 	case  5: sgr->attr |=  A_BLINK; break;
141*c77c4889SXin LI 	case 25: sgr->attr &= ~A_BLINK; break;
142*c77c4889SXin LI 
143*c77c4889SXin LI 	case  7: sgr->attr |=  A_INVERSE; break;
144*c77c4889SXin LI 	case 27: sgr->attr &= ~A_INVERSE; break;
145*c77c4889SXin LI 
146*c77c4889SXin LI 	case  8: sgr->attr |=  A_CONCEAL; break;
147*c77c4889SXin LI 	case 28: sgr->attr &= ~A_CONCEAL; break;
148*c77c4889SXin LI 
149*c77c4889SXin LI 	case 39: sgr->fg = C_DEFAULT; break;
150*c77c4889SXin LI 	case 49: sgr->bg = C_DEFAULT; break;
151*c77c4889SXin LI 
152*c77c4889SXin LI 	case 30: case 31: case 32: case 33:
153*c77c4889SXin LI 	case 34: case 35: case 36: case 37:
154*c77c4889SXin LI 		sgr->fg = C_ANSI(code - 30);
155*c77c4889SXin LI 		break;
156*c77c4889SXin LI 
157*c77c4889SXin LI 	case 40: case 41: case 42: case 43:
158*c77c4889SXin LI 	case 44: case 45: case 46: case 47:
159*c77c4889SXin LI 		sgr->bg = C_ANSI(code - 40);
160*c77c4889SXin LI 		break;
161*c77c4889SXin LI 	default:
162*c77c4889SXin LI 		return 1;
163*c77c4889SXin LI 	}
164*c77c4889SXin LI 
165*c77c4889SXin LI 	return 0;
166*c77c4889SXin LI }
167*c77c4889SXin LI 
168*c77c4889SXin LI static void set_win_colors(t_sgr *sgr)
169*c77c4889SXin LI {
1707374caaaSXin LI #if MSDOS_COMPILER==WIN32C
1717374caaaSXin LI 	/* Screen colors used by 3x and 4x SGR commands. */
1727374caaaSXin LI 	static unsigned char screen_color[] = {
1737374caaaSXin LI 		0, /* BLACK */
1747374caaaSXin LI 		FOREGROUND_RED,
1757374caaaSXin LI 		FOREGROUND_GREEN,
1767374caaaSXin LI 		FOREGROUND_RED|FOREGROUND_GREEN,
1777374caaaSXin LI 		FOREGROUND_BLUE,
1787374caaaSXin LI 		FOREGROUND_BLUE|FOREGROUND_RED,
1797374caaaSXin LI 		FOREGROUND_BLUE|FOREGROUND_GREEN,
1807374caaaSXin LI 		FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED
1817374caaaSXin LI 	};
1827374caaaSXin LI #else
183000ba3e8STim J. Robbins 	static enum COLORS screen_color[] = {
184000ba3e8STim J. Robbins 		BLACK, RED, GREEN, BROWN,
185000ba3e8STim J. Robbins 		BLUE, MAGENTA, CYAN, LIGHTGRAY
186000ba3e8STim J. Robbins 	};
1877374caaaSXin LI #endif
188000ba3e8STim J. Robbins 
189*c77c4889SXin LI 	int fg, bg, tmp;  /* Windows colors */
190*c77c4889SXin LI 
191*c77c4889SXin LI 	/* Not "SGR mode": apply -D<x> to default fg+bg with one attribute */
192*c77c4889SXin LI 	if (!sgr_mode && sgr->fg == C_DEFAULT && sgr->bg == C_DEFAULT)
193f6b74a7dSXin LI 	{
194*c77c4889SXin LI 		switch (sgr->attr)
195*c77c4889SXin LI 		{
196*c77c4889SXin LI 		case A_BOLD:
197*c77c4889SXin LI 			WIN32setcolors(bo_fg_color, bo_bg_color);
198*c77c4889SXin LI 			return;
199*c77c4889SXin LI 		case A_UNDERLINE:
200*c77c4889SXin LI 			WIN32setcolors(ul_fg_color, ul_bg_color);
201*c77c4889SXin LI 			return;
202*c77c4889SXin LI 		case A_BLINK:
203*c77c4889SXin LI 			WIN32setcolors(bl_fg_color, bl_bg_color);
204*c77c4889SXin LI 			return;
205*c77c4889SXin LI 		case A_INVERSE:
206*c77c4889SXin LI 			WIN32setcolors(so_fg_color, so_bg_color);
207*c77c4889SXin LI 			return;
208*c77c4889SXin LI 		/*
209*c77c4889SXin LI 		 * There's no -Di so italic should not be here, but to
210*c77c4889SXin LI 		 * preserve legacy behavior, apply -Ds to italic too.
211*c77c4889SXin LI 		 */
212*c77c4889SXin LI 		case A_ITALIC:
213*c77c4889SXin LI 			WIN32setcolors(so_fg_color, so_bg_color);
214*c77c4889SXin LI 			return;
215f6b74a7dSXin LI 		}
216*c77c4889SXin LI 	}
217*c77c4889SXin LI 
218*c77c4889SXin LI 	/* generic application of the SGR state as Windows colors */
219*c77c4889SXin LI 
220*c77c4889SXin LI 	fg = sgr->fg == C_DEFAULT ? nm_fg_color
221*c77c4889SXin LI 	                          : screen_color[CGET_ANSI(sgr->fg)];
222*c77c4889SXin LI 
223*c77c4889SXin LI 	bg = sgr->bg == C_DEFAULT ? nm_bg_color
224*c77c4889SXin LI 	                          : screen_color[CGET_ANSI(sgr->bg)];
225*c77c4889SXin LI 
226*c77c4889SXin LI 	if (sgr->attr & A_BOLD)
227*c77c4889SXin LI 		fg |= 8;
228*c77c4889SXin LI 
229*c77c4889SXin LI 	if (sgr->attr & (A_BLINK | A_UNDERLINE))
230*c77c4889SXin LI 		bg |= 8;  /* TODO: can be illegible */
231*c77c4889SXin LI 
232*c77c4889SXin LI 	if (sgr->attr & (A_INVERSE | A_ITALIC))
233*c77c4889SXin LI 	{
234*c77c4889SXin LI 		tmp = fg;
235*c77c4889SXin LI 		fg = bg;
236*c77c4889SXin LI 		bg = tmp;
237*c77c4889SXin LI 	}
238*c77c4889SXin LI 
239*c77c4889SXin LI 	if (sgr->attr & A_CONCEAL)
240*c77c4889SXin LI 		fg = bg ^ 8;
241*c77c4889SXin LI 
242*c77c4889SXin LI 	WIN32setcolors(fg, bg);
243*c77c4889SXin LI }
244*c77c4889SXin LI 
245*c77c4889SXin LI /* like is_ansi_end, but doesn't assume c != 0  (returns 0 for c == 0) */
246*c77c4889SXin LI static int is_ansi_end_0(char c)
247*c77c4889SXin LI {
248*c77c4889SXin LI 	return c && is_ansi_end((unsigned char)c);
249*c77c4889SXin LI }
250*c77c4889SXin LI 
251*c77c4889SXin LI static void win_flush(void)
252*c77c4889SXin LI {
253*c77c4889SXin LI 	if (ctldisp != OPT_ONPLUS
254*c77c4889SXin LI #if MSDOS_COMPILER==WIN32C
255*c77c4889SXin LI 	    || (vt_enabled && sgr_mode)
256*c77c4889SXin LI #endif
257*c77c4889SXin LI 	   )
258*c77c4889SXin LI 		WIN32textout(obuf, ptr_diff(ob, obuf));
259*c77c4889SXin LI 	else
260*c77c4889SXin LI 	{
261*c77c4889SXin LI 		/*
262*c77c4889SXin LI 		 * Digest text, apply embedded SGR sequences as Windows-colors.
263*c77c4889SXin LI 		 * By default - when -Da ("SGR mode") is unset - also apply
264*c77c4889SXin LI 		 * translation of -D command-line options (at set_win_colors)
265*c77c4889SXin LI 		 */
266*c77c4889SXin LI 		char *anchor, *p, *p_next;
267*c77c4889SXin LI 		static t_sgr sgr;
268*c77c4889SXin LI 
269*c77c4889SXin LI 		/* when unsupported SGR value is encountered, like 38/48 for
270*c77c4889SXin LI 		 * 256/true colors, then we abort processing this sequence,
271*c77c4889SXin LI 		 * because it may expect followup values, but we don't know
272*c77c4889SXin LI 		 * how many, so we've lost sync of this sequence parsing.
273*c77c4889SXin LI 		 * Without VT enabled it's OK because we can't do much anyway,
274*c77c4889SXin LI 		 * but with VT enabled we choose to passthrough this sequence
275*c77c4889SXin LI 		 * to the terminal - which can handle it better than us.
276*c77c4889SXin LI 		 * however, this means that our "sgr" var is no longer in sync
277*c77c4889SXin LI 		 * with the actual terminal state, which can lead to broken
278*c77c4889SXin LI 		 * colors with future sequences which we _can_ fully parse.
279*c77c4889SXin LI 		 * in such case, once it happens, we keep passthrough sequences
280*c77c4889SXin LI 		 * until we know we're in sync again - on a valid reset.
281*c77c4889SXin LI 		 */
282*c77c4889SXin LI 		static int sgr_bad_sync;
283*c77c4889SXin LI 
284000ba3e8STim J. Robbins 		for (anchor = p_next = obuf;
2857374caaaSXin LI 			 (p_next = memchr(p_next, ESC, ob - p_next)) != NULL; )
286000ba3e8STim J. Robbins 		{
287000ba3e8STim J. Robbins 			p = p_next;
2887374caaaSXin LI 			if (p[1] == '[')  /* "ESC-[" sequence */
289000ba3e8STim J. Robbins 			{
290*c77c4889SXin LI 				/*
291*c77c4889SXin LI 				* unknown SGR code ignores the rest of the seq,
292*c77c4889SXin LI 				* and allows ignoring sequences such as
293*c77c4889SXin LI 				* ^[[38;5;123m or ^[[38;2;5;6;7m
294*c77c4889SXin LI 				* (prior known codes at the same seq do apply)
295*c77c4889SXin LI 				*/
296*c77c4889SXin LI 				int bad_code = 0;
297*c77c4889SXin LI 
2987374caaaSXin LI 				if (p > anchor)
299000ba3e8STim J. Robbins 				{
300000ba3e8STim J. Robbins 					/*
301000ba3e8STim J. Robbins 					 * If some chars seen since
302000ba3e8STim J. Robbins 					 * the last escape sequence,
3037374caaaSXin LI 					 * write them out to the screen.
304000ba3e8STim J. Robbins 					 */
305*c77c4889SXin LI 					WIN32textout(anchor, ptr_diff(p, anchor));
306000ba3e8STim J. Robbins 					anchor = p;
307000ba3e8STim J. Robbins 				}
3087374caaaSXin LI 				p += 2;  /* Skip the "ESC-[" */
309*c77c4889SXin LI 				if (is_ansi_end_0(*p))
3107374caaaSXin LI 				{
3117374caaaSXin LI 					/*
3127374caaaSXin LI 					 * Handle null escape sequence
313*c77c4889SXin LI 					 * "ESC[m" as if it was "ESC[0m"
3147374caaaSXin LI 					 */
3157374caaaSXin LI 					p++;
3167374caaaSXin LI 					anchor = p_next = p;
317*c77c4889SXin LI 					update_sgr(&sgr, 0);
318*c77c4889SXin LI 					set_win_colors(&sgr);
319*c77c4889SXin LI 					sgr_bad_sync = 0;
3207374caaaSXin LI 					continue;
3217374caaaSXin LI 				}
322000ba3e8STim J. Robbins 				p_next = p;
3237374caaaSXin LI 
3247374caaaSXin LI 				/*
325*c77c4889SXin LI 				 * Parse and apply SGR values to the SGR state
3267374caaaSXin LI 				 * based on the escape sequence.
3277374caaaSXin LI 				 */
328*c77c4889SXin LI 				while (!is_ansi_end_0(*p))
329000ba3e8STim J. Robbins 				{
330000ba3e8STim J. Robbins 					char *q;
331000ba3e8STim J. Robbins 					long code = strtol(p, &q, 10);
332000ba3e8STim J. Robbins 
3337374caaaSXin LI 					if (*q == '\0')
334000ba3e8STim J. Robbins 					{
335000ba3e8STim J. Robbins 						/*
336000ba3e8STim J. Robbins 						 * Incomplete sequence.
337000ba3e8STim J. Robbins 						 * Leave it unprocessed
338000ba3e8STim J. Robbins 						 * in the buffer.
339000ba3e8STim J. Robbins 						 */
340*c77c4889SXin LI 						size_t slop = ptr_diff(q, anchor);
341*c77c4889SXin LI 						memmove(obuf, anchor, slop);
342000ba3e8STim J. Robbins 						ob = &obuf[slop];
343000ba3e8STim J. Robbins 						return;
344000ba3e8STim J. Robbins 					}
345000ba3e8STim J. Robbins 
3467374caaaSXin LI 					if (q == p ||
347*c77c4889SXin LI 						(!is_ansi_end_0(*q) && *q != ';'))
348000ba3e8STim J. Robbins 					{
349*c77c4889SXin LI 						/*
350*c77c4889SXin LI 						 * can't parse. passthrough
351*c77c4889SXin LI 						 * till the end of the buffer
352*c77c4889SXin LI 						 */
353000ba3e8STim J. Robbins 						p_next = q;
354000ba3e8STim J. Robbins 						break;
355000ba3e8STim J. Robbins 					}
356000ba3e8STim J. Robbins 					if (*q == ';')
357000ba3e8STim J. Robbins 						q++;
358000ba3e8STim J. Robbins 
359*c77c4889SXin LI 					if (!bad_code)
360*c77c4889SXin LI 						bad_code = update_sgr(&sgr, code);
361*c77c4889SXin LI 
362*c77c4889SXin LI 					if (bad_code)
363*c77c4889SXin LI 						sgr_bad_sync = 1;
364*c77c4889SXin LI 					else if (code == 0)
365*c77c4889SXin LI 						sgr_bad_sync = 0;
366*c77c4889SXin LI 
367000ba3e8STim J. Robbins 					p = q;
368000ba3e8STim J. Robbins 				}
369*c77c4889SXin LI 				if (!is_ansi_end_0(*p) || p == p_next)
370000ba3e8STim J. Robbins 					break;
371*c77c4889SXin LI 
372*c77c4889SXin LI 				if (sgr_bad_sync && vt_enabled) {
373*c77c4889SXin LI 					/* this or a prior sequence had unknown
374*c77c4889SXin LI 					 * SGR value. passthrough all sequences
375*c77c4889SXin LI 					 * until we're in-sync again
376f6b74a7dSXin LI 					 */
377*c77c4889SXin LI 					WIN32textout(anchor, ptr_diff(p+1, anchor));
378*c77c4889SXin LI 				} else {
379*c77c4889SXin LI 					set_win_colors(&sgr);
380f6b74a7dSXin LI 				}
3817374caaaSXin LI 				p_next = anchor = p + 1;
382000ba3e8STim J. Robbins 			} else
383000ba3e8STim J. Robbins 				p_next++;
384000ba3e8STim J. Robbins 		}
385000ba3e8STim J. Robbins 
386000ba3e8STim J. Robbins 		/* Output what's left in the buffer.  */
387*c77c4889SXin LI 		WIN32textout(anchor, ptr_diff(ob, anchor));
388000ba3e8STim J. Robbins 	}
389a5f0fb15SPaul Saab 	ob = obuf;
3902235c7feSXin LI }
3912235c7feSXin LI #endif
3922235c7feSXin LI 
3932235c7feSXin LI /*
3942235c7feSXin LI  * Flush buffered output.
3952235c7feSXin LI  *
3962235c7feSXin LI  * If we haven't displayed any file data yet,
3972235c7feSXin LI  * output messages on error output (file descriptor 2),
3982235c7feSXin LI  * otherwise output on standard output (file descriptor 1).
3992235c7feSXin LI  *
4002235c7feSXin LI  * This has the desirable effect of producing all
4012235c7feSXin LI  * error messages on error output if standard output
4022235c7feSXin LI  * is directed to a file.  It also does the same if
4032235c7feSXin LI  * we never produce any real output; for example, if
4042235c7feSXin LI  * the input file(s) cannot be opened.  If we do
4052235c7feSXin LI  * eventually produce output, code in edit() makes
4062235c7feSXin LI  * sure these messages can be seen before they are
4072235c7feSXin LI  * overwritten or scrolled away.
4082235c7feSXin LI  */
409d713e089SXin LI public void flush(void)
4102235c7feSXin LI {
411*c77c4889SXin LI 	size_t n;
4122235c7feSXin LI 
413*c77c4889SXin LI 	n = ptr_diff(ob, obuf);
4142235c7feSXin LI 	if (n == 0)
4152235c7feSXin LI 		return;
4162235c7feSXin LI 	ob = obuf;
4172235c7feSXin LI 
4182235c7feSXin LI #if MSDOS_COMPILER==MSOFTC
4192235c7feSXin LI 	if (interactive())
4202235c7feSXin LI 	{
4212235c7feSXin LI 		obuf[n] = '\0';
4222235c7feSXin LI 		_outtext(obuf);
4232235c7feSXin LI 		return;
4242235c7feSXin LI 	}
4252235c7feSXin LI #else
4262235c7feSXin LI #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
4272235c7feSXin LI 	if (interactive())
4282235c7feSXin LI 	{
4292235c7feSXin LI 		ob = obuf + n;
4302235c7feSXin LI 		*ob = '\0';
4312235c7feSXin LI 		win_flush();
432a5f0fb15SPaul Saab 		return;
433a5f0fb15SPaul Saab 	}
434a5f0fb15SPaul Saab #endif
435a5f0fb15SPaul Saab #endif
4362235c7feSXin LI 
4372235c7feSXin LI 	if (write(outfd, obuf, n) != n)
438*c77c4889SXin LI 		screen_trashed();
4392235c7feSXin LI }
4402235c7feSXin LI 
4412235c7feSXin LI /*
4422235c7feSXin LI  * Set the output file descriptor (1=stdout or 2=stderr).
4432235c7feSXin LI  */
444d713e089SXin LI public void set_output(int fd)
4452235c7feSXin LI {
4462235c7feSXin LI 	flush();
4472235c7feSXin LI 	outfd = fd;
448a5f0fb15SPaul Saab }
449a5f0fb15SPaul Saab 
450a5f0fb15SPaul Saab /*
451a5f0fb15SPaul Saab  * Output a character.
452*c77c4889SXin LI  * ch is int for compatibility with tputs.
453a5f0fb15SPaul Saab  */
454*c77c4889SXin LI public int putchr(int ch)
455a5f0fb15SPaul Saab {
456*c77c4889SXin LI 	char c = (char) ch;
4576dcb072bSXin LI #if 0 /* fake UTF-8 output for testing */
4586dcb072bSXin LI 	extern int utf_mode;
4596dcb072bSXin LI 	if (utf_mode)
4606dcb072bSXin LI 	{
4616dcb072bSXin LI 		static char ubuf[MAX_UTF_CHAR_LEN];
4626dcb072bSXin LI 		static int ubuf_len = 0;
4636dcb072bSXin LI 		static int ubuf_index = 0;
4646dcb072bSXin LI 		if (ubuf_len == 0)
4656dcb072bSXin LI 		{
4666dcb072bSXin LI 			ubuf_len = utf_len(c);
4676dcb072bSXin LI 			ubuf_index = 0;
4686dcb072bSXin LI 		}
4696dcb072bSXin LI 		ubuf[ubuf_index++] = c;
4706dcb072bSXin LI 		if (ubuf_index < ubuf_len)
4716dcb072bSXin LI 			return c;
4726dcb072bSXin LI 		c = get_wchar(ubuf) & 0xFF;
4736dcb072bSXin LI 		ubuf_len = 0;
4746dcb072bSXin LI 	}
4756dcb072bSXin LI #endif
47630a1828cSXin LI 	clear_bot_if_needed();
477a5f0fb15SPaul Saab #if MSDOS_COMPILER
478a5f0fb15SPaul Saab 	if (c == '\n' && is_tty)
479a5f0fb15SPaul Saab 	{
480a5f0fb15SPaul Saab 		/* remove_top(1); */
481a5f0fb15SPaul Saab 		putchr('\r');
482a5f0fb15SPaul Saab 	}
483a5f0fb15SPaul Saab #else
484a5f0fb15SPaul Saab #ifdef _OSK
485a5f0fb15SPaul Saab 	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
486a5f0fb15SPaul Saab 		putchr(0x0A);
487a5f0fb15SPaul Saab #endif
488a5f0fb15SPaul Saab #endif
489a5f0fb15SPaul Saab 	/*
490a5f0fb15SPaul Saab 	 * Some versions of flush() write to *ob, so we must flush
491a5f0fb15SPaul Saab 	 * when we are still one char from the end of obuf.
492a5f0fb15SPaul Saab 	 */
493a5f0fb15SPaul Saab 	if (ob >= &obuf[sizeof(obuf)-1])
494a5f0fb15SPaul Saab 		flush();
495a5f0fb15SPaul Saab 	*ob++ = c;
4967f074f9cSXin LI 	at_prompt = 0;
497a5f0fb15SPaul Saab 	return (c);
498a5f0fb15SPaul Saab }
499a5f0fb15SPaul Saab 
500d713e089SXin LI public void clear_bot_if_needed(void)
50130a1828cSXin LI {
50230a1828cSXin LI 	if (!need_clr)
50330a1828cSXin LI 		return;
50430a1828cSXin LI 	need_clr = 0;
50530a1828cSXin LI 	clear_bot();
50630a1828cSXin LI }
50730a1828cSXin LI 
508a5f0fb15SPaul Saab /*
509a5f0fb15SPaul Saab  * Output a string.
510a5f0fb15SPaul Saab  */
511d713e089SXin LI public void putstr(constant char *s)
512a5f0fb15SPaul Saab {
513a5f0fb15SPaul Saab 	while (*s != '\0')
514a5f0fb15SPaul Saab 		putchr(*s++);
515a5f0fb15SPaul Saab }
516a5f0fb15SPaul Saab 
517a5f0fb15SPaul Saab 
518a5f0fb15SPaul Saab /*
519000ba3e8STim J. Robbins  * Convert an integral type to a string.
520000ba3e8STim J. Robbins  */
521000ba3e8STim J. Robbins #define TYPE_TO_A_FUNC(funcname, type) \
522d713e089SXin LI void funcname(type num, char *buf, int radix) \
523000ba3e8STim J. Robbins { \
524000ba3e8STim J. Robbins 	int neg = (num < 0); \
525000ba3e8STim J. Robbins 	char tbuf[INT_STRLEN_BOUND(num)+2]; \
5261ea31627SRobert Watson 	char *s = tbuf + sizeof(tbuf); \
527000ba3e8STim J. Robbins 	if (neg) num = -num; \
528000ba3e8STim J. Robbins 	*--s = '\0'; \
529000ba3e8STim J. Robbins 	do { \
530d713e089SXin LI 		*--s = "0123456789ABCDEF"[num % radix]; \
531d713e089SXin LI 	} while ((num /= radix) != 0); \
532000ba3e8STim J. Robbins 	if (neg) *--s = '-'; \
533000ba3e8STim J. Robbins 	strcpy(buf, s); \
534000ba3e8STim J. Robbins }
535000ba3e8STim J. Robbins 
536000ba3e8STim J. Robbins TYPE_TO_A_FUNC(postoa, POSITION)
537000ba3e8STim J. Robbins TYPE_TO_A_FUNC(linenumtoa, LINENUM)
538000ba3e8STim J. Robbins TYPE_TO_A_FUNC(inttoa, int)
539000ba3e8STim J. Robbins 
540000ba3e8STim J. Robbins /*
541d713e089SXin LI  * Convert a string to an integral type.  Return ((type) -1) on overflow.
542b7780dbeSXin LI  */
543*c77c4889SXin LI #define STR_TO_TYPE_FUNC(funcname, cfuncname, type) \
544*c77c4889SXin LI type cfuncname(constant char *buf, constant char **ebuf, int radix) \
545b7780dbeSXin LI { \
546b7780dbeSXin LI 	type val = 0; \
547*c77c4889SXin LI 	lbool v = 0; \
5482235c7feSXin LI 	for (;; buf++) { \
5492235c7feSXin LI 		char c = *buf; \
550d713e089SXin LI 		int digit = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; \
551d713e089SXin LI 		if (digit < 0 || digit >= radix) break; \
552*c77c4889SXin LI 		v = v || ckd_mul(&val, val, radix); \
553*c77c4889SXin LI 		v = v || ckd_add(&val, val, digit); \
554b7780dbeSXin LI 	} \
555b7780dbeSXin LI 	if (ebuf != NULL) *ebuf = buf; \
556*c77c4889SXin LI 	return v ? (type)(-1) : val; \
557*c77c4889SXin LI } \
558*c77c4889SXin LI type funcname(char *buf, char **ebuf, int radix) \
559*c77c4889SXin LI { \
560*c77c4889SXin LI 	constant char *cbuf = buf; \
561*c77c4889SXin LI 	type r = cfuncname(cbuf, &cbuf, radix); \
562*c77c4889SXin LI 	if (ebuf != NULL) *ebuf = (char *) cbuf; /*{{const-issue}}*/ \
563*c77c4889SXin LI 	return r; \
564b7780dbeSXin LI }
565b7780dbeSXin LI 
566*c77c4889SXin LI STR_TO_TYPE_FUNC(lstrtopos, lstrtoposc, POSITION)
567*c77c4889SXin LI STR_TO_TYPE_FUNC(lstrtoi, lstrtoic, int)
568*c77c4889SXin LI STR_TO_TYPE_FUNC(lstrtoul, lstrtoulc, unsigned long)
569b7780dbeSXin LI 
570b7780dbeSXin LI /*
571d713e089SXin LI  * Print an integral type.
572a5f0fb15SPaul Saab  */
573d713e089SXin LI #define IPRINT_FUNC(funcname, type, typetoa) \
574d713e089SXin LI static int funcname(type num, int radix) \
575d713e089SXin LI { \
576d713e089SXin LI 	char buf[INT_STRLEN_BOUND(num)]; \
577d713e089SXin LI 	typetoa(num, buf, radix); \
578d713e089SXin LI 	putstr(buf); \
579d713e089SXin LI 	return (int) strlen(buf); \
580000ba3e8STim J. Robbins }
581a5f0fb15SPaul Saab 
582d713e089SXin LI IPRINT_FUNC(iprint_int, int, inttoa)
583d713e089SXin LI IPRINT_FUNC(iprint_linenum, LINENUM, linenumtoa)
584a5f0fb15SPaul Saab 
585a5f0fb15SPaul Saab /*
586a5f0fb15SPaul Saab  * This function implements printf-like functionality
587a5f0fb15SPaul Saab  * using a more portable argument list mechanism than printf's.
5882235c7feSXin LI  *
5892235c7feSXin LI  * {{ This paranoia about the portability of printf dates from experiences
5902235c7feSXin LI  *    with systems in the 1980s and is of course no longer necessary. }}
591a5f0fb15SPaul Saab  */
592*c77c4889SXin LI public int less_printf(constant char *fmt, PARG *parg)
593a5f0fb15SPaul Saab {
594*c77c4889SXin LI 	constant char *s;
595*c77c4889SXin LI 	constant char *es;
5961ea31627SRobert Watson 	int col;
597a5f0fb15SPaul Saab 
598a5f0fb15SPaul Saab 	col = 0;
599a5f0fb15SPaul Saab 	while (*fmt != '\0')
600a5f0fb15SPaul Saab 	{
601a5f0fb15SPaul Saab 		if (*fmt != '%')
602a5f0fb15SPaul Saab 		{
603a5f0fb15SPaul Saab 			putchr(*fmt++);
604a5f0fb15SPaul Saab 			col++;
605a5f0fb15SPaul Saab 		} else
606a5f0fb15SPaul Saab 		{
607a5f0fb15SPaul Saab 			++fmt;
608000ba3e8STim J. Robbins 			switch (*fmt++)
609000ba3e8STim J. Robbins 			{
610a5f0fb15SPaul Saab 			case 's':
611a5f0fb15SPaul Saab 				s = parg->p_string;
612*c77c4889SXin LI 				es = s + strlen(s);
613a5f0fb15SPaul Saab 				parg++;
614a5f0fb15SPaul Saab 				while (*s != '\0')
615a5f0fb15SPaul Saab 				{
616*c77c4889SXin LI 					LWCHAR ch = step_charc(&s, +1, es);
617*c77c4889SXin LI 					constant char *ps = utf_mode ? prutfchar(ch) : prchar(ch);
618*c77c4889SXin LI 					while (*ps != '\0')
619*c77c4889SXin LI 					{
620*c77c4889SXin LI 						putchr(*ps++);
621a5f0fb15SPaul Saab 						col++;
622a5f0fb15SPaul Saab 					}
623*c77c4889SXin LI 				}
624a5f0fb15SPaul Saab 				break;
625a5f0fb15SPaul Saab 			case 'd':
626d713e089SXin LI 				col += iprint_int(parg->p_int, 10);
627d713e089SXin LI 				parg++;
628d713e089SXin LI 				break;
629d713e089SXin LI 			case 'x':
630d713e089SXin LI 				col += iprint_int(parg->p_int, 16);
631a5f0fb15SPaul Saab 				parg++;
632000ba3e8STim J. Robbins 				break;
633000ba3e8STim J. Robbins 			case 'n':
634d713e089SXin LI 				col += iprint_linenum(parg->p_linenum, 10);
635000ba3e8STim J. Robbins 				parg++;
636a5f0fb15SPaul Saab 				break;
6372235c7feSXin LI 			case 'c':
638*c77c4889SXin LI 				s = prchar((LWCHAR) parg->p_char);
63995270f73SXin LI 				parg++;
64095270f73SXin LI 				while (*s != '\0')
64195270f73SXin LI 				{
64295270f73SXin LI 					putchr(*s++);
6432235c7feSXin LI 					col++;
64495270f73SXin LI 				}
6452235c7feSXin LI 				break;
646b2ea2440SXin LI 			case '%':
647b2ea2440SXin LI 				putchr('%');
648b2ea2440SXin LI 				break;
649a5f0fb15SPaul Saab 			}
650a5f0fb15SPaul Saab 		}
651a5f0fb15SPaul Saab 	}
652a5f0fb15SPaul Saab 	return (col);
653a5f0fb15SPaul Saab }
654a5f0fb15SPaul Saab 
655a5f0fb15SPaul Saab /*
656a5f0fb15SPaul Saab  * Get a RETURN.
657a5f0fb15SPaul Saab  * If some other non-trivial char is pressed, unget it, so it will
658a5f0fb15SPaul Saab  * become the next command.
659a5f0fb15SPaul Saab  */
660d713e089SXin LI public void get_return(void)
661a5f0fb15SPaul Saab {
662a5f0fb15SPaul Saab 	int c;
663a5f0fb15SPaul Saab 
664a5f0fb15SPaul Saab #if ONLY_RETURN
665a5f0fb15SPaul Saab 	while ((c = getchr()) != '\n' && c != '\r')
666a5f0fb15SPaul Saab 		bell();
667a5f0fb15SPaul Saab #else
668a5f0fb15SPaul Saab 	c = getchr();
669a5f0fb15SPaul Saab 	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
670*c77c4889SXin LI 		ungetcc((char) c);
671a5f0fb15SPaul Saab #endif
672a5f0fb15SPaul Saab }
673a5f0fb15SPaul Saab 
674a5f0fb15SPaul Saab /*
675a5f0fb15SPaul Saab  * Output a message in the lower left corner of the screen
676a5f0fb15SPaul Saab  * and wait for carriage return.
677a5f0fb15SPaul Saab  */
678*c77c4889SXin LI public void error(constant char *fmt, PARG *parg)
679a5f0fb15SPaul Saab {
680a5f0fb15SPaul Saab 	int col = 0;
681a5f0fb15SPaul Saab 	static char return_to_continue[] = "  (press RETURN)";
682a5f0fb15SPaul Saab 
683a5f0fb15SPaul Saab 	errmsgs++;
684a5f0fb15SPaul Saab 
6852235c7feSXin LI 	if (!interactive())
686a5f0fb15SPaul Saab 	{
6872235c7feSXin LI 		less_printf(fmt, parg);
688a5f0fb15SPaul Saab 		putchr('\n');
689a5f0fb15SPaul Saab 		return;
690a5f0fb15SPaul Saab 	}
691a5f0fb15SPaul Saab 
6922235c7feSXin LI 	if (!oldbot)
6932235c7feSXin LI 		squish_check();
6942235c7feSXin LI 	at_exit();
6952235c7feSXin LI 	clear_bot();
6962235c7feSXin LI 	at_enter(AT_STANDOUT|AT_COLOR_ERROR);
6972235c7feSXin LI 	col += so_s_width;
6982235c7feSXin LI 	col += less_printf(fmt, parg);
699a5f0fb15SPaul Saab 	putstr(return_to_continue);
7006dcb072bSXin LI 	at_exit();
701*c77c4889SXin LI 	col += (int) sizeof(return_to_continue) + so_e_width;
702a5f0fb15SPaul Saab 
703a5f0fb15SPaul Saab 	get_return();
704a5f0fb15SPaul Saab 	lower_left();
70533096f16SXin LI 	clear_eol();
706a5f0fb15SPaul Saab 
707a5f0fb15SPaul Saab 	if (col >= sc_width)
708a5f0fb15SPaul Saab 		/*
709a5f0fb15SPaul Saab 		 * Printing the message has probably scrolled the screen.
710a5f0fb15SPaul Saab 		 * {{ Unless the terminal doesn't have auto margins,
711a5f0fb15SPaul Saab 		 *    in which case we just hammered on the right margin. }}
712a5f0fb15SPaul Saab 		 */
713*c77c4889SXin LI 		screen_trashed();
714a5f0fb15SPaul Saab 
715a5f0fb15SPaul Saab 	flush();
716a5f0fb15SPaul Saab }
717a5f0fb15SPaul Saab 
718a5f0fb15SPaul Saab /*
719a5f0fb15SPaul Saab  * Output a message in the lower left corner of the screen
720a5f0fb15SPaul Saab  * and don't wait for carriage return.
721a5f0fb15SPaul Saab  * Usually used to warn that we are beginning a potentially
722a5f0fb15SPaul Saab  * time-consuming operation.
723a5f0fb15SPaul Saab  */
724*c77c4889SXin LI static void ierror_suffix(constant char *fmt, PARG *parg, constant char *suffix1, constant char *suffix2, constant char *suffix3)
725a5f0fb15SPaul Saab {
7266dcb072bSXin LI 	at_exit();
727a5f0fb15SPaul Saab 	clear_bot();
7282235c7feSXin LI 	at_enter(AT_STANDOUT|AT_COLOR_ERROR);
729a5f0fb15SPaul Saab 	(void) less_printf(fmt, parg);
730d713e089SXin LI 	putstr(suffix1);
731d713e089SXin LI 	putstr(suffix2);
732d713e089SXin LI 	putstr(suffix3);
7336dcb072bSXin LI 	at_exit();
734a5f0fb15SPaul Saab 	flush();
735a5f0fb15SPaul Saab 	need_clr = 1;
736a5f0fb15SPaul Saab }
737a5f0fb15SPaul Saab 
738*c77c4889SXin LI public void ierror(constant char *fmt, PARG *parg)
739d713e089SXin LI {
740d713e089SXin LI 	ierror_suffix(fmt, parg, "... (interrupt to abort)", "", "");
741d713e089SXin LI }
742d713e089SXin LI 
743*c77c4889SXin LI public void ixerror(constant char *fmt, PARG *parg)
744d713e089SXin LI {
745d713e089SXin LI 	if (!supports_ctrl_x())
746d713e089SXin LI 		ierror(fmt, parg);
747d713e089SXin LI 	else
748*c77c4889SXin LI 	{
749*c77c4889SXin LI 		char ichar[MAX_PRCHAR_LEN+1];
750*c77c4889SXin LI 		strcpy(ichar, prchar((LWCHAR) intr_char));
751*c77c4889SXin LI 		ierror_suffix(fmt, parg, "... (", ichar, " or interrupt to abort)");
752*c77c4889SXin LI 	}
753d713e089SXin LI }
754d713e089SXin LI 
755a5f0fb15SPaul Saab /*
756a5f0fb15SPaul Saab  * Output a message in the lower left corner of the screen
757a5f0fb15SPaul Saab  * and return a single-character response.
758a5f0fb15SPaul Saab  */
759*c77c4889SXin LI public int query(constant char *fmt, PARG *parg)
760a5f0fb15SPaul Saab {
7611ea31627SRobert Watson 	int c;
762a5f0fb15SPaul Saab 	int col = 0;
763a5f0fb15SPaul Saab 
7642235c7feSXin LI 	if (interactive())
765a5f0fb15SPaul Saab 		clear_bot();
766a5f0fb15SPaul Saab 
767a5f0fb15SPaul Saab 	(void) less_printf(fmt, parg);
768a5f0fb15SPaul Saab 	c = getchr();
769a5f0fb15SPaul Saab 
7702235c7feSXin LI 	if (interactive())
771a5f0fb15SPaul Saab 	{
772a5f0fb15SPaul Saab 		lower_left();
773a5f0fb15SPaul Saab 		if (col >= sc_width)
774*c77c4889SXin LI 			screen_trashed();
775a5f0fb15SPaul Saab 		flush();
7762235c7feSXin LI 	} else
7772235c7feSXin LI 	{
7782235c7feSXin LI 		putchr('\n');
7792235c7feSXin LI 	}
780a5f0fb15SPaul Saab 
7812235c7feSXin LI 	if (c == 'Q')
7822235c7feSXin LI 		quit(QUIT_OK);
783a5f0fb15SPaul Saab 	return (c);
784a5f0fb15SPaul Saab }
785