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