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