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