xref: /freebsd/contrib/less/output.c (revision 252d6dde57d5dd0184929d1f8fb65e7713f51c6d)
1 /*
2  * Copyright (C) 1984-2025  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 
23 public int errmsgs;    /* Count of messages displayed by error() */
24 public int need_clr;
25 public int final_attr;
26 public int at_prompt;
27 
28 extern int sigs;
29 extern int sc_width;
30 extern int so_s_width, so_e_width;
31 extern int is_tty;
32 extern int oldbot;
33 extern int utf_mode;
34 extern char intr_char;
35 
36 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
37 extern int ctldisp;
38 extern int nm_fg_color, nm_bg_color;
39 extern int bo_fg_color, bo_bg_color;
40 extern int ul_fg_color, ul_bg_color;
41 extern int so_fg_color, so_bg_color;
42 extern int bl_fg_color, bl_bg_color;
43 extern int sgr_mode;
44 #if MSDOS_COMPILER==WIN32C
45 extern int vt_enabled;
46 #endif
47 #endif
48 
49 /*
50  * Display the line which is in the line buffer.
51  */
put_line(lbool forw_scroll)52 public void put_line(lbool forw_scroll)
53 {
54 	int c;
55 	size_t i;
56 	int a;
57 
58 	if (ABORT_SIGS())
59 	{
60 		/*
61 		 * Don't output if a signal is pending.
62 		 */
63 		screen_trashed();
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 	at_exit();
79 
80 	if (forw_scroll && should_clear_after_line())
81 		clear_eol();
82 }
83 
84 /*
85  * win_flush has at least one non-critical issue when an escape sequence
86  * begins at the last char of the buffer, and possibly more issues.
87  * as a temporary measure to reduce likelyhood of encountering end-of-buffer
88  * issues till the SGR parser is replaced, OUTBUF_SIZE is 8K on Windows.
89  */
90 static char obuf[OUTBUF_SIZE];
91 static char *ob = obuf;
92 static int outfd = 2; /* stderr */
93 
94 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
95 
96 typedef unsigned t_attr;
97 
98 #define A_BOLD      (1u<<0)
99 #define A_ITALIC    (1u<<1)
100 #define A_UNDERLINE (1u<<2)
101 #define A_BLINK     (1u<<3)
102 #define A_INVERSE   (1u<<4)
103 #define A_CONCEAL   (1u<<5)
104 
105 /* long is guaranteed 32 bits, and we reserve bits for type + RGB */
106 typedef unsigned long t_color;
107 
108 #define T_DEFAULT   0ul
109 #define T_ANSI      1ul  /* colors 0-7 */
110 
111 #define CGET_ANSI(c) ((c) & 0x7)
112 
113 #define C_DEFAULT    (T_DEFAULT <<24) /* 0 */
114 #define C_ANSI(c)   ((T_ANSI    <<24) | (c))
115 
116 /* attr/fg/bg/all 0 is the default attr/fg/bg/all, respectively */
117 typedef struct t_sgr {
118 	t_attr attr;
119 	t_color fg;
120 	t_color bg;
121 } t_sgr;
122 
123 static constant t_sgr SGR_DEFAULT; /* = {0} */
124 
125 /* returns 0 on success, non-0 on unknown SGR code */
update_sgr(t_sgr * sgr,long code)126 static int update_sgr(t_sgr *sgr, long code)
127 {
128 	switch (code)
129 	{
130 	case  0: *sgr = SGR_DEFAULT; break;
131 
132 	case  1: sgr->attr |=  A_BOLD; break;
133 	case 22: sgr->attr &= ~A_BOLD; break;
134 
135 	case  3: sgr->attr |=  A_ITALIC; break;
136 	case 23: sgr->attr &= ~A_ITALIC; break;
137 
138 	case  4: sgr->attr |=  A_UNDERLINE; break;
139 	case 24: sgr->attr &= ~A_UNDERLINE; break;
140 
141 	case  6: /* fast-blink, fallthrough */
142 	case  5: sgr->attr |=  A_BLINK; break;
143 	case 25: sgr->attr &= ~A_BLINK; break;
144 
145 	case  7: sgr->attr |=  A_INVERSE; break;
146 	case 27: sgr->attr &= ~A_INVERSE; break;
147 
148 	case  8: sgr->attr |=  A_CONCEAL; break;
149 	case 28: sgr->attr &= ~A_CONCEAL; break;
150 
151 	case 39: sgr->fg = C_DEFAULT; break;
152 	case 49: sgr->bg = C_DEFAULT; break;
153 
154 	case 30: case 31: case 32: case 33:
155 	case 34: case 35: case 36: case 37:
156 		sgr->fg = C_ANSI(code - 30);
157 		break;
158 
159 	case 40: case 41: case 42: case 43:
160 	case 44: case 45: case 46: case 47:
161 		sgr->bg = C_ANSI(code - 40);
162 		break;
163 	default:
164 		return 1;
165 	}
166 
167 	return 0;
168 }
169 
set_win_colors(t_sgr * sgr)170 static void set_win_colors(t_sgr *sgr)
171 {
172 #if MSDOS_COMPILER==WIN32C
173 	/* Screen colors used by 3x and 4x SGR commands. */
174 	static unsigned char screen_color[] = {
175 		0, /* BLACK */
176 		FOREGROUND_RED,
177 		FOREGROUND_GREEN,
178 		FOREGROUND_RED|FOREGROUND_GREEN,
179 		FOREGROUND_BLUE,
180 		FOREGROUND_BLUE|FOREGROUND_RED,
181 		FOREGROUND_BLUE|FOREGROUND_GREEN,
182 		FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED
183 	};
184 #else
185 	static enum COLORS screen_color[] = {
186 		BLACK, RED, GREEN, BROWN,
187 		BLUE, MAGENTA, CYAN, LIGHTGRAY
188 	};
189 #endif
190 
191 	int fg, bg, tmp;  /* Windows colors */
192 
193 	/* Not "SGR mode": apply -D<x> to default fg+bg with one attribute */
194 	if (!sgr_mode && sgr->fg == C_DEFAULT && sgr->bg == C_DEFAULT)
195 	{
196 		switch (sgr->attr)
197 		{
198 		case A_BOLD:
199 			WIN32setcolors(bo_fg_color, bo_bg_color);
200 			return;
201 		case A_UNDERLINE:
202 			WIN32setcolors(ul_fg_color, ul_bg_color);
203 			return;
204 		case A_BLINK:
205 			WIN32setcolors(bl_fg_color, bl_bg_color);
206 			return;
207 		case A_INVERSE:
208 			WIN32setcolors(so_fg_color, so_bg_color);
209 			return;
210 		/*
211 		 * There's no -Di so italic should not be here, but to
212 		 * preserve legacy behavior, apply -Ds to italic too.
213 		 */
214 		case A_ITALIC:
215 			WIN32setcolors(so_fg_color, so_bg_color);
216 			return;
217 		}
218 	}
219 
220 	/* generic application of the SGR state as Windows colors */
221 
222 	fg = sgr->fg == C_DEFAULT ? nm_fg_color
223 	                          : screen_color[CGET_ANSI(sgr->fg)];
224 
225 	bg = sgr->bg == C_DEFAULT ? nm_bg_color
226 	                          : screen_color[CGET_ANSI(sgr->bg)];
227 
228 	if (sgr->attr & A_BOLD)
229 		fg |= 8;
230 
231 	if (sgr->attr & (A_BLINK | A_UNDERLINE))
232 		bg |= 8;  /* TODO: can be illegible */
233 
234 	if (sgr->attr & (A_INVERSE | A_ITALIC))
235 	{
236 		tmp = fg;
237 		fg = bg;
238 		bg = tmp;
239 	}
240 
241 	if (sgr->attr & A_CONCEAL)
242 		fg = bg ^ 8;
243 
244 	WIN32setcolors(fg, bg);
245 }
246 
247 /* like is_ansi_end, but doesn't assume c != 0  (returns 0 for c == 0) */
is_ansi_end_0(char c)248 static lbool is_ansi_end_0(char c)
249 {
250 	return c != '\0' && is_ansi_end((unsigned char)c);
251 }
252 
win_flush(void)253 static void win_flush(void)
254 {
255 #if MSDOS_COMPILER != WIN32C
256 	static constant int vt_enabled = 0;
257 #endif
258 	if (ctldisp != OPT_ONPLUS || (vt_enabled && sgr_mode))
259 		WIN32textout(obuf, ptr_diff(ob, obuf));
260 	else
261 	{
262 		/*
263 		 * Digest text, apply embedded SGR sequences as Windows-colors.
264 		 * By default - when -Da ("SGR mode") is unset - also apply
265 		 * translation of -D command-line options (at set_win_colors)
266 		 */
267 		char *anchor, *p, *p_next;
268 		static t_sgr sgr;
269 
270 		/* when unsupported SGR value is encountered, like 38/48 for
271 		 * 256/true colors, then we abort processing this sequence,
272 		 * because it may expect followup values, but we don't know
273 		 * how many, so we've lost sync of this sequence parsing.
274 		 * Without VT enabled it's OK because we can't do much anyway,
275 		 * but with VT enabled we choose to passthrough this sequence
276 		 * to the terminal - which can handle it better than us.
277 		 * however, this means that our "sgr" var is no longer in sync
278 		 * with the actual terminal state, which can lead to broken
279 		 * colors with future sequences which we _can_ fully parse.
280 		 * in such case, once it happens, we keep passthrough sequences
281 		 * until we know we're in sync again - on a valid reset.
282 		 */
283 		static int sgr_bad_sync;
284 
285 		for (anchor = p_next = obuf;
286 			 (p_next = memchr(p_next, ESC, ob - p_next)) != NULL; )
287 		{
288 			p = p_next;
289 			if (p[1] == '[')  /* "ESC-[" sequence */
290 			{
291 				/*
292 				* unknown SGR code ignores the rest of the seq,
293 				* and allows ignoring sequences such as
294 				* ^[[38;5;123m or ^[[38;2;5;6;7m
295 				* (prior known codes at the same seq do apply)
296 				*/
297 				int bad_code = 0;
298 
299 				if (p > anchor)
300 				{
301 					/*
302 					 * If some chars seen since
303 					 * the last escape sequence,
304 					 * write them out to the screen.
305 					 */
306 					WIN32textout(anchor, ptr_diff(p, anchor));
307 					anchor = p;
308 				}
309 				p += 2;  /* Skip the "ESC-[" */
310 				if (is_ansi_end_0(*p))
311 				{
312 					/*
313 					 * Handle null escape sequence
314 					 * "ESC[m" as if it was "ESC[0m"
315 					 */
316 					p++;
317 					anchor = p_next = p;
318 					update_sgr(&sgr, 0);
319 					set_win_colors(&sgr);
320 					sgr_bad_sync = 0;
321 					continue;
322 				}
323 				p_next = p;
324 
325 				/*
326 				 * Parse and apply SGR values to the SGR state
327 				 * based on the escape sequence.
328 				 */
329 				while (!is_ansi_end_0(*p))
330 				{
331 					char *q;
332 					long code = strtol(p, &q, 10);
333 
334 					if (*q == '\0')
335 					{
336 						/*
337 						 * Incomplete sequence.
338 						 * Leave it unprocessed
339 						 * in the buffer.
340 						 */
341 						size_t slop = ptr_diff(q, anchor);
342 						memmove(obuf, anchor, slop);
343 						ob = &obuf[slop];
344 						return;
345 					}
346 
347 					if (q == p ||
348 						(!is_ansi_end_0(*q) && *q != ';'))
349 					{
350 						/*
351 						 * can't parse. passthrough
352 						 * till the end of the buffer
353 						 */
354 						p_next = q;
355 						break;
356 					}
357 					if (*q == ';')
358 						q++;
359 
360 					if (!bad_code)
361 						bad_code = update_sgr(&sgr, code);
362 
363 					if (bad_code)
364 						sgr_bad_sync = 1;
365 					else if (code == 0)
366 						sgr_bad_sync = 0;
367 
368 					p = q;
369 				}
370 				if (!is_ansi_end_0(*p) || p == p_next)
371 					break;
372 
373 				if (sgr_bad_sync && vt_enabled) {
374 					/* this or a prior sequence had unknown
375 					 * SGR value. passthrough all sequences
376 					 * until we're in-sync again
377 					 */
378 					WIN32textout(anchor, ptr_diff(p+1, anchor));
379 				} else {
380 					set_win_colors(&sgr);
381 				}
382 				p_next = anchor = p + 1;
383 			} else
384 				p_next++;
385 		}
386 
387 		/* Output what's left in the buffer.  */
388 		WIN32textout(anchor, ptr_diff(ob, anchor));
389 	}
390 	ob = obuf;
391 }
392 #endif
393 
394 /*
395  * Flush buffered output.
396  *
397  * If we haven't displayed any file data yet,
398  * output messages on error output (file descriptor 2),
399  * otherwise output on standard output (file descriptor 1).
400  *
401  * This has the desirable effect of producing all
402  * error messages on error output if standard output
403  * is directed to a file.  It also does the same if
404  * we never produce any real output; for example, if
405  * the input file(s) cannot be opened.  If we do
406  * eventually produce output, code in edit() makes
407  * sure these messages can be seen before they are
408  * overwritten or scrolled away.
409  */
flush(void)410 public void flush(void)
411 {
412 	size_t n;
413 
414 	n = ptr_diff(ob, obuf);
415 	if (n == 0)
416 		return;
417 	ob = obuf;
418 
419 #if MSDOS_COMPILER==MSOFTC
420 	if (interactive())
421 	{
422 		obuf[n] = '\0';
423 		_outtext(obuf);
424 		return;
425 	}
426 #else
427 #if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
428 	if (interactive())
429 	{
430 		ob = obuf + n;
431 		*ob = '\0';
432 		win_flush();
433 		return;
434 	}
435 #endif
436 #endif
437 
438 	if (write(outfd, obuf, n) != n)
439 		screen_trashed();
440 }
441 
442 /*
443  * Set the output file descriptor (1=stdout or 2=stderr).
444  */
set_output(int fd)445 public void set_output(int fd)
446 {
447 	flush();
448 	outfd = fd;
449 }
450 
451 /*
452  * Output a character.
453  * ch is int for compatibility with tputs.
454  */
putchr(int ch)455 public int putchr(int ch)
456 {
457 	char c = (char) ch;
458 #if 0 /* fake UTF-8 output for testing */
459 	extern int utf_mode;
460 	if (utf_mode)
461 	{
462 		static char ubuf[MAX_UTF_CHAR_LEN];
463 		static int ubuf_len = 0;
464 		static int ubuf_index = 0;
465 		if (ubuf_len == 0)
466 		{
467 			ubuf_len = utf_len(c);
468 			ubuf_index = 0;
469 		}
470 		ubuf[ubuf_index++] = c;
471 		if (ubuf_index < ubuf_len)
472 			return c;
473 		c = get_wchar(ubuf) & 0xFF;
474 		ubuf_len = 0;
475 	}
476 #endif
477 	clear_bot_if_needed();
478 #if MSDOS_COMPILER
479 	if (c == '\n' && is_tty)
480 	{
481 		/* remove_top(1); */
482 		putchr('\r');
483 	}
484 #else
485 #ifdef _OSK
486 	if (c == '\n' && is_tty)  /* In OS-9, '\n' == 0x0D */
487 		putchr(0x0A);
488 #endif
489 #endif
490 	/*
491 	 * Some versions of flush() write to *ob, so we must flush
492 	 * when we are still one char from the end of obuf.
493 	 */
494 	if (ob >= &obuf[sizeof(obuf)-1])
495 		flush();
496 	*ob++ = c;
497 	at_prompt = 0;
498 	return (c);
499 }
500 
clear_bot_if_needed(void)501 public void clear_bot_if_needed(void)
502 {
503 	if (!need_clr)
504 		return;
505 	need_clr = 0;
506 	clear_bot();
507 }
508 
509 /*
510  * Output a string.
511  */
putstr(constant char * s)512 public void putstr(constant char *s)
513 {
514 	while (*s != '\0')
515 		putchr(*s++);
516 }
517 
518 
519 /*
520  * Convert an integral type to a string.
521  */
522 #define TYPE_TO_A_FUNC(funcname, type) \
523 void funcname(type num, char *buf, int radix) \
524 { \
525 	int neg = (num < 0); \
526 	char tbuf[INT_STRLEN_BOUND(num)+2]; \
527 	char *s = tbuf + sizeof(tbuf); \
528 	if (neg) num = -num; \
529 	*--s = '\0'; \
530 	do { \
531 		*--s = "0123456789ABCDEF"[num % radix]; \
532 	} while ((num /= radix) != 0); \
533 	if (neg) *--s = '-'; \
534 	strcpy(buf, s); \
535 }
536 
TYPE_TO_A_FUNC(postoa,POSITION)537 TYPE_TO_A_FUNC(postoa, POSITION)
538 TYPE_TO_A_FUNC(linenumtoa, LINENUM)
539 TYPE_TO_A_FUNC(inttoa, int)
540 
541 /*
542  * Convert a string to an integral type.  Return ((type) -1) on overflow.
543  */
544 #define STR_TO_TYPE_FUNC(funcname, cfuncname, type) \
545 type cfuncname(constant char *buf, constant char **ebuf, int radix) \
546 { \
547 	type val = 0; \
548 	lbool v = 0; \
549 	for (;; buf++) { \
550 		char c = *buf; \
551 		int digit = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; \
552 		if (digit < 0 || digit >= radix) break; \
553 		v = v || ckd_mul(&val, val, radix); \
554 		v = v || ckd_add(&val, val, digit); \
555 	} \
556 	if (ebuf != NULL) *ebuf = buf; \
557 	return v ? (type)(-1) : val; \
558 } \
559 type funcname(char *buf, char **ebuf, int radix) \
560 { \
561 	constant char *cbuf = buf; \
562 	type r = cfuncname(cbuf, &cbuf, radix); \
563 	if (ebuf != NULL) *ebuf = (char *) cbuf; /*{{const-issue}}*/ \
564 	return r; \
565 }
566 
567 STR_TO_TYPE_FUNC(lstrtopos, lstrtoposc, POSITION)
568 STR_TO_TYPE_FUNC(lstrtoi, lstrtoic, int)
569 STR_TO_TYPE_FUNC(lstrtoul, lstrtoulc, unsigned long)
570 
571 /*
572  * Print an integral type.
573  */
574 #define IPRINT_FUNC(funcname, type, typetoa) \
575 static int funcname(type num, int radix) \
576 { \
577 	char buf[INT_STRLEN_BOUND(num)]; \
578 	typetoa(num, buf, radix); \
579 	putstr(buf); \
580 	return (int) strlen(buf); \
581 }
582 
583 IPRINT_FUNC(iprint_int, int, inttoa)
584 IPRINT_FUNC(iprint_linenum, LINENUM, linenumtoa)
585 
586 /*
587  * This function implements printf-like functionality
588  * using a more portable argument list mechanism than printf's.
589  *
590  * {{ This paranoia about the portability of printf dates from experiences
591  *    with systems in the 1980s and is of course no longer necessary. }}
592  */
593 public int less_printf(constant char *fmt, PARG *parg)
594 {
595 	constant char *s;
596 	constant char *es;
597 	int col;
598 
599 	col = 0;
600 	while (*fmt != '\0')
601 	{
602 		if (*fmt != '%')
603 		{
604 			putchr(*fmt++);
605 			col++;
606 		} else
607 		{
608 			++fmt;
609 			switch (*fmt++)
610 			{
611 			case 's':
612 				s = parg->p_string;
613 				es = s + strlen(s);
614 				parg++;
615 				while (*s != '\0')
616 				{
617 					LWCHAR ch = step_charc(&s, +1, es);
618 					constant char *ps = utf_mode ? prutfchar(ch) : prchar(ch);
619 					while (*ps != '\0')
620 					{
621 						putchr(*ps++);
622 						col++;
623 					}
624 				}
625 				break;
626 			case 'd':
627 				col += iprint_int(parg->p_int, 10);
628 				parg++;
629 				break;
630 			case 'x':
631 				col += iprint_int(parg->p_int, 16);
632 				parg++;
633 				break;
634 			case 'n':
635 				col += iprint_linenum(parg->p_linenum, 10);
636 				parg++;
637 				break;
638 			case 'c':
639 				s = prchar((LWCHAR) parg->p_char);
640 				parg++;
641 				while (*s != '\0')
642 				{
643 					putchr(*s++);
644 					col++;
645 				}
646 				break;
647 			case '%':
648 				putchr('%');
649 				break;
650 			}
651 		}
652 	}
653 	return (col);
654 }
655 
656 /*
657  * Get a RETURN.
658  * If some other non-trivial char is pressed, unget it, so it will
659  * become the next command.
660  */
get_return(void)661 public void get_return(void)
662 {
663 	int c;
664 
665 #if ONLY_RETURN
666 	while ((c = getchr()) != '\n' && c != '\r')
667 		bell();
668 #else
669 	c = getchr();
670 	if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
671 		ungetcc((char) c);
672 #endif
673 }
674 
675 /*
676  * Output a message in the lower left corner of the screen
677  * and wait for carriage return.
678  */
error(constant char * fmt,PARG * parg)679 public void error(constant char *fmt, PARG *parg)
680 {
681 	int col = 0;
682 	static char return_to_continue[] = "  (press RETURN)";
683 
684 	errmsgs++;
685 
686 	if (!interactive())
687 	{
688 		less_printf(fmt, parg);
689 		putchr('\n');
690 		return;
691 	}
692 
693 	if (!oldbot)
694 		squish_check();
695 	at_exit();
696 	clear_bot();
697 	at_enter(AT_STANDOUT|AT_COLOR_ERROR);
698 	col += so_s_width;
699 	col += less_printf(fmt, parg);
700 	putstr(return_to_continue);
701 	at_exit();
702 	col += (int) sizeof(return_to_continue) + so_e_width;
703 
704 	get_return();
705 	lower_left();
706 	clear_eol();
707 
708 	if (col >= sc_width)
709 		/*
710 		 * Printing the message has probably scrolled the screen.
711 		 * {{ Unless the terminal doesn't have auto margins,
712 		 *    in which case we just hammered on the right margin. }}
713 		 */
714 		screen_trashed();
715 
716 	flush();
717 }
718 
719 /*
720  * Output a message in the lower left corner of the screen
721  * and don't wait for carriage return.
722  * Usually used to warn that we are beginning a potentially
723  * time-consuming operation.
724  */
ierror_suffix(constant char * fmt,PARG * parg,constant char * suffix1,constant char * suffix2,constant char * suffix3)725 static void ierror_suffix(constant char *fmt, PARG *parg, constant char *suffix1, constant char *suffix2, constant char *suffix3)
726 {
727 	at_exit();
728 	clear_bot();
729 	at_enter(AT_STANDOUT|AT_COLOR_ERROR);
730 	(void) less_printf(fmt, parg);
731 	putstr(suffix1);
732 	putstr(suffix2);
733 	putstr(suffix3);
734 	at_exit();
735 	flush();
736 	need_clr = 1;
737 }
738 
ierror(constant char * fmt,PARG * parg)739 public void ierror(constant char *fmt, PARG *parg)
740 {
741 	ierror_suffix(fmt, parg, "... (interrupt to abort)", "", "");
742 }
743 
ixerror(constant char * fmt,PARG * parg)744 public void ixerror(constant char *fmt, PARG *parg)
745 {
746 	if (!supports_ctrl_x())
747 		ierror(fmt, parg);
748 	else
749 	{
750 		char ichar[MAX_PRCHAR_LEN+1];
751 		strcpy(ichar, prchar((LWCHAR) intr_char));
752 		ierror_suffix(fmt, parg, "... (", ichar, " or interrupt to abort)");
753 	}
754 }
755 
756 /*
757  * Output a message in the lower left corner of the screen
758  * and return a single-character response.
759  */
query(constant char * fmt,PARG * parg)760 public int query(constant char *fmt, PARG *parg)
761 {
762 	int c;
763 	int col = 0;
764 
765 	if (interactive())
766 		clear_bot();
767 
768 	(void) less_printf(fmt, parg);
769 	c = getchr();
770 
771 	if (interactive())
772 	{
773 		lower_left();
774 		if (col >= sc_width)
775 			screen_trashed();
776 		flush();
777 	} else
778 	{
779 		putchr('\n');
780 	}
781 
782 	if (c == 'Q')
783 		quit(QUIT_OK);
784 	return (c);
785 }
786