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