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