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