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