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