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 int need_clr;
25 public int final_attr;
26
27 extern int sigs;
28 extern int sc_width;
29 extern int so_s_width, so_e_width;
30 extern int is_tty;
31 extern int oldbot;
32 extern int utf_mode;
33 extern char intr_char;
34 extern lbool term_init_ever;
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 likelihood 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) != (ssize_t) n)
439 screen_trashed();
440 }
441
442 /*
443 * Set the output file descriptor (1=stdout or 2=stderr).
444 */
set_output(int fd,lbool no_term_init)445 public void set_output(int fd, lbool no_term_init)
446 {
447 flush();
448 outfd = fd;
449 if (no_term_init)
450 term_init_ever = TRUE; /* don't init terminal in putchr */
451 }
452
453 /*
454 * Output a character.
455 * ch is int for compatibility with tputs.
456 */
putchr(int ch)457 public int putchr(int ch)
458 {
459 char c = (char) ch;
460
461 /*
462 * Init the terminal if this is the first byte written to stdout
463 * (rather than stderr), and the terminal has never been initted.
464 * If it has previously been initted, it will be reinitted explicitly
465 * as part of a term_deinit/term_init pair, so we shouldn't do it here.
466 */
467 if (!term_init_ever && outfd == 1)
468 term_init();
469
470 #if 0 /* fake UTF-8 output for testing */
471 if (utf_mode)
472 {
473 static char ubuf[MAX_UTF_CHAR_LEN];
474 static int ubuf_len = 0;
475 static int ubuf_index = 0;
476 if (ubuf_len == 0)
477 {
478 ubuf_len = utf_len(c);
479 ubuf_index = 0;
480 }
481 ubuf[ubuf_index++] = c;
482 if (ubuf_index < ubuf_len)
483 return c;
484 c = get_wchar(ubuf) & 0xFF;
485 ubuf_len = 0;
486 }
487 #endif
488 clear_bot_if_needed();
489 #if MSDOS_COMPILER
490 if (c == '\n' && is_tty)
491 {
492 /* remove_top(1); */
493 putchr('\r');
494 }
495 #else
496 #ifdef _OSK
497 if (c == '\n' && is_tty) /* In OS-9, '\n' == 0x0D */
498 putchr(0x0A);
499 #endif
500 #endif
501 /*
502 * Some versions of flush() write to *ob, so we must flush
503 * when we are still one char from the end of obuf.
504 */
505 if (ob >= &obuf[sizeof(obuf)-1])
506 flush();
507 *ob++ = c;
508 return (c);
509 }
510
clear_bot_if_needed(void)511 public void clear_bot_if_needed(void)
512 {
513 if (!need_clr)
514 return;
515 need_clr = 0;
516 clear_bot();
517 }
518
519 /*
520 * Output a string.
521 */
putstr(constant char * s)522 public void putstr(constant char *s)
523 {
524 while (*s != '\0')
525 putchr(*s++);
526 }
527
528
529 /*
530 * Convert an integral type to a string.
531 */
532 #define TYPE_TO_A_FUNC(funcname, type) \
533 void funcname(type num, char *buf, int radix) \
534 { \
535 int neg = (num < 0); \
536 char tbuf[INT_STRLEN_BOUND(num)+2]; \
537 char *s = tbuf + sizeof(tbuf); \
538 if (neg) num = -num; \
539 *--s = '\0'; \
540 do { \
541 *--s = "0123456789ABCDEF"[num % radix]; \
542 } while ((num /= radix) != 0); \
543 if (neg) *--s = '-'; \
544 strcpy(buf, s); \
545 }
546
TYPE_TO_A_FUNC(postoa,POSITION)547 TYPE_TO_A_FUNC(postoa, POSITION)
548 TYPE_TO_A_FUNC(linenumtoa, LINENUM)
549 TYPE_TO_A_FUNC(inttoa, int)
550
551 /*
552 * Convert a string to an integral type. Return ((type) -1) on overflow.
553 */
554 #define STR_TO_TYPE_FUNC(funcname, cfuncname, type) \
555 type cfuncname(constant char *buf, constant char **ebuf, int radix) \
556 { \
557 type val = 0; \
558 lbool v = 0; \
559 for (;; buf++) { \
560 char c = *buf; \
561 int digit = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; \
562 if (digit < 0 || digit >= radix) break; \
563 v = v || ckd_mul(&val, val, radix); \
564 v = v || ckd_add(&val, val, digit); \
565 } \
566 if (ebuf != NULL) *ebuf = buf; \
567 return v ? (type)(-1) : val; \
568 } \
569 type funcname(char *buf, char **ebuf, int radix) \
570 { \
571 constant char *cbuf = buf; \
572 type r = cfuncname(cbuf, &cbuf, radix); \
573 if (ebuf != NULL) *ebuf = (char *) cbuf; /*{{const-issue}}*/ \
574 return r; \
575 }
576
577 STR_TO_TYPE_FUNC(lstrtopos, lstrtoposc, POSITION)
578 STR_TO_TYPE_FUNC(lstrtoi, lstrtoic, int)
579 STR_TO_TYPE_FUNC(lstrtoul, lstrtoulc, unsigned long)
580
581 /*
582 * Print an integral type.
583 */
584 #define IPRINT_FUNC(funcname, type, typetoa) \
585 static int funcname(type num, int radix) \
586 { \
587 char buf[INT_STRLEN_BOUND(num)]; \
588 typetoa(num, buf, radix); \
589 putstr(buf); \
590 return (int) strlen(buf); \
591 }
592
593 IPRINT_FUNC(iprint_int, int, inttoa)
594 IPRINT_FUNC(iprint_linenum, LINENUM, linenumtoa)
595
596 /*
597 * This function implements printf-like functionality
598 * using a more portable argument list mechanism than printf's.
599 *
600 * {{ This paranoia about the portability of printf dates from experiences
601 * with systems in the 1980s and is of course no longer necessary. }}
602 */
603 public int less_printf(constant char *fmt, constant PARG *parg)
604 {
605 constant char *s;
606 constant char *es;
607 int col;
608
609 col = 0;
610 while (*fmt != '\0')
611 {
612 if (*fmt != '%')
613 {
614 putchr(*fmt++);
615 col++;
616 } else
617 {
618 ++fmt;
619 switch (*fmt++)
620 {
621 case 's':
622 s = parg->p_string;
623 es = s + strlen(s);
624 parg++;
625 while (*s != '\0')
626 {
627 LWCHAR ch = step_charc(&s, +1, es);
628 constant char *ps = utf_mode ? prutfchar(ch) : prchar(ch);
629 while (*ps != '\0')
630 {
631 putchr(*ps++);
632 col++;
633 }
634 }
635 break;
636 case 'd':
637 col += iprint_int(parg->p_int, 10);
638 parg++;
639 break;
640 case 'x':
641 col += iprint_int(parg->p_int, 16);
642 parg++;
643 break;
644 case 'n':
645 col += iprint_linenum(parg->p_linenum, 10);
646 parg++;
647 break;
648 case 'c':
649 s = prchar((LWCHAR) parg->p_char);
650 parg++;
651 while (*s != '\0')
652 {
653 putchr(*s++);
654 col++;
655 }
656 break;
657 case '%':
658 putchr('%');
659 break;
660 }
661 }
662 }
663 return (col);
664 }
665
666 /*
667 * Get a RETURN.
668 * If some other non-trivial char is pressed, unget it, so it will
669 * become the next command.
670 */
get_return(void)671 public void get_return(void)
672 {
673 int c;
674
675 #if ONLY_RETURN
676 while ((c = getchr()) != '\n' && c != '\r')
677 lbell();
678 #else
679 c = getchr();
680 if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
681 ungetcc((char) c);
682 #endif
683 }
684
685 /*
686 * Output a message in the lower left corner of the screen
687 * and wait for carriage return.
688 */
error(constant char * fmt,constant PARG * parg)689 public void error(constant char *fmt, constant PARG *parg)
690 {
691 int col = 0;
692 static char return_to_continue[] = " (press RETURN)";
693
694 errmsgs++;
695
696 if (!interactive())
697 {
698 less_printf(fmt, parg);
699 putchr('\n');
700 return;
701 }
702
703 if (!oldbot)
704 squish_check();
705 at_exit();
706 clear_bot();
707 at_enter(AT_STANDOUT|AT_COLOR_ERROR);
708 col += so_s_width;
709 col += less_printf(fmt, parg);
710 putstr(return_to_continue);
711 at_exit();
712 col += (int) sizeof(return_to_continue) + so_e_width;
713
714 get_return();
715 lower_left();
716 clear_eol();
717
718 if (col >= sc_width)
719 /*
720 * Printing the message has probably scrolled the screen.
721 * {{ Unless the terminal doesn't have auto margins,
722 * in which case we just hammered on the right margin. }}
723 */
724 screen_trashed();
725
726 flush();
727 }
728
729 /*
730 * Output a message in the lower left corner of the screen
731 * and don't wait for carriage return.
732 * Usually used to warn that we are beginning a potentially
733 * time-consuming operation.
734 */
ierror_suffix(constant char * fmt,constant PARG * parg,constant char * suffix1,constant char * suffix2,constant char * suffix3)735 static void ierror_suffix(constant char *fmt, constant PARG *parg, constant char *suffix1, constant char *suffix2, constant char *suffix3)
736 {
737 at_exit();
738 clear_bot();
739 at_enter(AT_STANDOUT|AT_COLOR_ERROR);
740 (void) less_printf(fmt, parg);
741 putstr(suffix1);
742 putstr(suffix2);
743 putstr(suffix3);
744 at_exit();
745 flush();
746 need_clr = 1;
747 }
748
ierror(constant char * fmt,constant PARG * parg)749 public void ierror(constant char *fmt, constant PARG *parg)
750 {
751 ierror_suffix(fmt, parg, "... (interrupt to abort)", "", "");
752 }
753
ixerror(constant char * fmt,constant PARG * parg)754 public void ixerror(constant char *fmt, constant PARG *parg)
755 {
756 if (!supports_ctrl_x())
757 ierror(fmt, parg);
758 else
759 {
760 char ichar[MAX_PRCHAR_LEN+1];
761 strcpy(ichar, prchar((LWCHAR) intr_char));
762 ierror_suffix(fmt, parg, "... (", ichar, " or interrupt to abort)");
763 }
764 }
765
766 /*
767 * Output a message in the lower left corner of the screen
768 * and return a single-character response.
769 */
query(constant char * fmt,constant PARG * parg)770 public int query(constant char *fmt, constant PARG *parg)
771 {
772 int c;
773 int col = 0;
774
775 if (interactive())
776 clear_bot();
777
778 (void) less_printf(fmt, parg);
779 c = getchr();
780
781 if (interactive())
782 {
783 lower_left();
784 if (col >= sc_width)
785 screen_trashed();
786 flush();
787 } else
788 {
789 putchr('\n');
790 }
791
792 if (c == 'Q')
793 quit(QUIT_OK);
794 return (c);
795 }
796