Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/less/output.c
39476 views
1
/*
2
* Copyright (C) 1984-2025 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 is_tty;
32
extern int oldbot;
33
extern int utf_mode;
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
*/
52
public void put_line(lbool forw_scroll)
53
{
54
int c;
55
size_t i;
56
int a;
57
58
if (ABORT_SIGS())
59
{
60
/*
61
* Don't output if a signal is pending.
62
*/
63
screen_trashed();
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
at_exit();
79
80
if (forw_scroll && should_clear_after_line())
81
clear_eol();
82
}
83
84
/*
85
* win_flush has at least one non-critical issue when an escape sequence
86
* begins at the last char of the buffer, and possibly more issues.
87
* as a temporary measure to reduce likelyhood of encountering end-of-buffer
88
* issues till the SGR parser is replaced, OUTBUF_SIZE is 8K on Windows.
89
*/
90
static char obuf[OUTBUF_SIZE];
91
static char *ob = obuf;
92
static int outfd = 2; /* stderr */
93
94
#if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
95
96
typedef unsigned t_attr;
97
98
#define A_BOLD (1u<<0)
99
#define A_ITALIC (1u<<1)
100
#define A_UNDERLINE (1u<<2)
101
#define A_BLINK (1u<<3)
102
#define A_INVERSE (1u<<4)
103
#define A_CONCEAL (1u<<5)
104
105
/* long is guaranteed 32 bits, and we reserve bits for type + RGB */
106
typedef unsigned long t_color;
107
108
#define T_DEFAULT 0ul
109
#define T_ANSI 1ul /* colors 0-7 */
110
111
#define CGET_ANSI(c) ((c) & 0x7)
112
113
#define C_DEFAULT (T_DEFAULT <<24) /* 0 */
114
#define C_ANSI(c) ((T_ANSI <<24) | (c))
115
116
/* attr/fg/bg/all 0 is the default attr/fg/bg/all, respectively */
117
typedef struct t_sgr {
118
t_attr attr;
119
t_color fg;
120
t_color bg;
121
} t_sgr;
122
123
static constant t_sgr SGR_DEFAULT; /* = {0} */
124
125
/* returns 0 on success, non-0 on unknown SGR code */
126
static int update_sgr(t_sgr *sgr, long code)
127
{
128
switch (code)
129
{
130
case 0: *sgr = SGR_DEFAULT; break;
131
132
case 1: sgr->attr |= A_BOLD; break;
133
case 22: sgr->attr &= ~A_BOLD; break;
134
135
case 3: sgr->attr |= A_ITALIC; break;
136
case 23: sgr->attr &= ~A_ITALIC; break;
137
138
case 4: sgr->attr |= A_UNDERLINE; break;
139
case 24: sgr->attr &= ~A_UNDERLINE; break;
140
141
case 6: /* fast-blink, fallthrough */
142
case 5: sgr->attr |= A_BLINK; break;
143
case 25: sgr->attr &= ~A_BLINK; break;
144
145
case 7: sgr->attr |= A_INVERSE; break;
146
case 27: sgr->attr &= ~A_INVERSE; break;
147
148
case 8: sgr->attr |= A_CONCEAL; break;
149
case 28: sgr->attr &= ~A_CONCEAL; break;
150
151
case 39: sgr->fg = C_DEFAULT; break;
152
case 49: sgr->bg = C_DEFAULT; break;
153
154
case 30: case 31: case 32: case 33:
155
case 34: case 35: case 36: case 37:
156
sgr->fg = C_ANSI(code - 30);
157
break;
158
159
case 40: case 41: case 42: case 43:
160
case 44: case 45: case 46: case 47:
161
sgr->bg = C_ANSI(code - 40);
162
break;
163
default:
164
return 1;
165
}
166
167
return 0;
168
}
169
170
static void set_win_colors(t_sgr *sgr)
171
{
172
#if MSDOS_COMPILER==WIN32C
173
/* Screen colors used by 3x and 4x SGR commands. */
174
static unsigned char screen_color[] = {
175
0, /* BLACK */
176
FOREGROUND_RED,
177
FOREGROUND_GREEN,
178
FOREGROUND_RED|FOREGROUND_GREEN,
179
FOREGROUND_BLUE,
180
FOREGROUND_BLUE|FOREGROUND_RED,
181
FOREGROUND_BLUE|FOREGROUND_GREEN,
182
FOREGROUND_BLUE|FOREGROUND_GREEN|FOREGROUND_RED
183
};
184
#else
185
static enum COLORS screen_color[] = {
186
BLACK, RED, GREEN, BROWN,
187
BLUE, MAGENTA, CYAN, LIGHTGRAY
188
};
189
#endif
190
191
int fg, bg, tmp; /* Windows colors */
192
193
/* Not "SGR mode": apply -D<x> to default fg+bg with one attribute */
194
if (!sgr_mode && sgr->fg == C_DEFAULT && sgr->bg == C_DEFAULT)
195
{
196
switch (sgr->attr)
197
{
198
case A_BOLD:
199
WIN32setcolors(bo_fg_color, bo_bg_color);
200
return;
201
case A_UNDERLINE:
202
WIN32setcolors(ul_fg_color, ul_bg_color);
203
return;
204
case A_BLINK:
205
WIN32setcolors(bl_fg_color, bl_bg_color);
206
return;
207
case A_INVERSE:
208
WIN32setcolors(so_fg_color, so_bg_color);
209
return;
210
/*
211
* There's no -Di so italic should not be here, but to
212
* preserve legacy behavior, apply -Ds to italic too.
213
*/
214
case A_ITALIC:
215
WIN32setcolors(so_fg_color, so_bg_color);
216
return;
217
}
218
}
219
220
/* generic application of the SGR state as Windows colors */
221
222
fg = sgr->fg == C_DEFAULT ? nm_fg_color
223
: screen_color[CGET_ANSI(sgr->fg)];
224
225
bg = sgr->bg == C_DEFAULT ? nm_bg_color
226
: screen_color[CGET_ANSI(sgr->bg)];
227
228
if (sgr->attr & A_BOLD)
229
fg |= 8;
230
231
if (sgr->attr & (A_BLINK | A_UNDERLINE))
232
bg |= 8; /* TODO: can be illegible */
233
234
if (sgr->attr & (A_INVERSE | A_ITALIC))
235
{
236
tmp = fg;
237
fg = bg;
238
bg = tmp;
239
}
240
241
if (sgr->attr & A_CONCEAL)
242
fg = bg ^ 8;
243
244
WIN32setcolors(fg, bg);
245
}
246
247
/* like is_ansi_end, but doesn't assume c != 0 (returns 0 for c == 0) */
248
static lbool is_ansi_end_0(char c)
249
{
250
return c != '\0' && is_ansi_end((unsigned char)c);
251
}
252
253
static void win_flush(void)
254
{
255
#if MSDOS_COMPILER != WIN32C
256
static constant int vt_enabled = 0;
257
#endif
258
if (ctldisp != OPT_ONPLUS || (vt_enabled && sgr_mode))
259
WIN32textout(obuf, ptr_diff(ob, obuf));
260
else
261
{
262
/*
263
* Digest text, apply embedded SGR sequences as Windows-colors.
264
* By default - when -Da ("SGR mode") is unset - also apply
265
* translation of -D command-line options (at set_win_colors)
266
*/
267
char *anchor, *p, *p_next;
268
static t_sgr sgr;
269
270
/* when unsupported SGR value is encountered, like 38/48 for
271
* 256/true colors, then we abort processing this sequence,
272
* because it may expect followup values, but we don't know
273
* how many, so we've lost sync of this sequence parsing.
274
* Without VT enabled it's OK because we can't do much anyway,
275
* but with VT enabled we choose to passthrough this sequence
276
* to the terminal - which can handle it better than us.
277
* however, this means that our "sgr" var is no longer in sync
278
* with the actual terminal state, which can lead to broken
279
* colors with future sequences which we _can_ fully parse.
280
* in such case, once it happens, we keep passthrough sequences
281
* until we know we're in sync again - on a valid reset.
282
*/
283
static int sgr_bad_sync;
284
285
for (anchor = p_next = obuf;
286
(p_next = memchr(p_next, ESC, ob - p_next)) != NULL; )
287
{
288
p = p_next;
289
if (p[1] == '[') /* "ESC-[" sequence */
290
{
291
/*
292
* unknown SGR code ignores the rest of the seq,
293
* and allows ignoring sequences such as
294
* ^[[38;5;123m or ^[[38;2;5;6;7m
295
* (prior known codes at the same seq do apply)
296
*/
297
int bad_code = 0;
298
299
if (p > anchor)
300
{
301
/*
302
* If some chars seen since
303
* the last escape sequence,
304
* write them out to the screen.
305
*/
306
WIN32textout(anchor, ptr_diff(p, anchor));
307
anchor = p;
308
}
309
p += 2; /* Skip the "ESC-[" */
310
if (is_ansi_end_0(*p))
311
{
312
/*
313
* Handle null escape sequence
314
* "ESC[m" as if it was "ESC[0m"
315
*/
316
p++;
317
anchor = p_next = p;
318
update_sgr(&sgr, 0);
319
set_win_colors(&sgr);
320
sgr_bad_sync = 0;
321
continue;
322
}
323
p_next = p;
324
325
/*
326
* Parse and apply SGR values to the SGR state
327
* based on the escape sequence.
328
*/
329
while (!is_ansi_end_0(*p))
330
{
331
char *q;
332
long code = strtol(p, &q, 10);
333
334
if (*q == '\0')
335
{
336
/*
337
* Incomplete sequence.
338
* Leave it unprocessed
339
* in the buffer.
340
*/
341
size_t slop = ptr_diff(q, anchor);
342
memmove(obuf, anchor, slop);
343
ob = &obuf[slop];
344
return;
345
}
346
347
if (q == p ||
348
(!is_ansi_end_0(*q) && *q != ';'))
349
{
350
/*
351
* can't parse. passthrough
352
* till the end of the buffer
353
*/
354
p_next = q;
355
break;
356
}
357
if (*q == ';')
358
q++;
359
360
if (!bad_code)
361
bad_code = update_sgr(&sgr, code);
362
363
if (bad_code)
364
sgr_bad_sync = 1;
365
else if (code == 0)
366
sgr_bad_sync = 0;
367
368
p = q;
369
}
370
if (!is_ansi_end_0(*p) || p == p_next)
371
break;
372
373
if (sgr_bad_sync && vt_enabled) {
374
/* this or a prior sequence had unknown
375
* SGR value. passthrough all sequences
376
* until we're in-sync again
377
*/
378
WIN32textout(anchor, ptr_diff(p+1, anchor));
379
} else {
380
set_win_colors(&sgr);
381
}
382
p_next = anchor = p + 1;
383
} else
384
p_next++;
385
}
386
387
/* Output what's left in the buffer. */
388
WIN32textout(anchor, ptr_diff(ob, anchor));
389
}
390
ob = obuf;
391
}
392
#endif
393
394
/*
395
* Flush buffered output.
396
*
397
* If we haven't displayed any file data yet,
398
* output messages on error output (file descriptor 2),
399
* otherwise output on standard output (file descriptor 1).
400
*
401
* This has the desirable effect of producing all
402
* error messages on error output if standard output
403
* is directed to a file. It also does the same if
404
* we never produce any real output; for example, if
405
* the input file(s) cannot be opened. If we do
406
* eventually produce output, code in edit() makes
407
* sure these messages can be seen before they are
408
* overwritten or scrolled away.
409
*/
410
public void flush(void)
411
{
412
size_t n;
413
414
n = ptr_diff(ob, obuf);
415
if (n == 0)
416
return;
417
ob = obuf;
418
419
#if MSDOS_COMPILER==MSOFTC
420
if (interactive())
421
{
422
obuf[n] = '\0';
423
_outtext(obuf);
424
return;
425
}
426
#else
427
#if MSDOS_COMPILER==WIN32C || MSDOS_COMPILER==BORLANDC || MSDOS_COMPILER==DJGPPC
428
if (interactive())
429
{
430
ob = obuf + n;
431
*ob = '\0';
432
win_flush();
433
return;
434
}
435
#endif
436
#endif
437
438
if (write(outfd, obuf, n) != n)
439
screen_trashed();
440
}
441
442
/*
443
* Set the output file descriptor (1=stdout or 2=stderr).
444
*/
445
public void set_output(int fd)
446
{
447
flush();
448
outfd = fd;
449
}
450
451
/*
452
* Output a character.
453
* ch is int for compatibility with tputs.
454
*/
455
public int putchr(int ch)
456
{
457
char c = (char) ch;
458
#if 0 /* fake UTF-8 output for testing */
459
extern int utf_mode;
460
if (utf_mode)
461
{
462
static char ubuf[MAX_UTF_CHAR_LEN];
463
static int ubuf_len = 0;
464
static int ubuf_index = 0;
465
if (ubuf_len == 0)
466
{
467
ubuf_len = utf_len(c);
468
ubuf_index = 0;
469
}
470
ubuf[ubuf_index++] = c;
471
if (ubuf_index < ubuf_len)
472
return c;
473
c = get_wchar(ubuf) & 0xFF;
474
ubuf_len = 0;
475
}
476
#endif
477
clear_bot_if_needed();
478
#if MSDOS_COMPILER
479
if (c == '\n' && is_tty)
480
{
481
/* remove_top(1); */
482
putchr('\r');
483
}
484
#else
485
#ifdef _OSK
486
if (c == '\n' && is_tty) /* In OS-9, '\n' == 0x0D */
487
putchr(0x0A);
488
#endif
489
#endif
490
/*
491
* Some versions of flush() write to *ob, so we must flush
492
* when we are still one char from the end of obuf.
493
*/
494
if (ob >= &obuf[sizeof(obuf)-1])
495
flush();
496
*ob++ = c;
497
at_prompt = 0;
498
return (c);
499
}
500
501
public void clear_bot_if_needed(void)
502
{
503
if (!need_clr)
504
return;
505
need_clr = 0;
506
clear_bot();
507
}
508
509
/*
510
* Output a string.
511
*/
512
public void putstr(constant char *s)
513
{
514
while (*s != '\0')
515
putchr(*s++);
516
}
517
518
519
/*
520
* Convert an integral type to a string.
521
*/
522
#define TYPE_TO_A_FUNC(funcname, type) \
523
void funcname(type num, char *buf, int radix) \
524
{ \
525
int neg = (num < 0); \
526
char tbuf[INT_STRLEN_BOUND(num)+2]; \
527
char *s = tbuf + sizeof(tbuf); \
528
if (neg) num = -num; \
529
*--s = '\0'; \
530
do { \
531
*--s = "0123456789ABCDEF"[num % radix]; \
532
} while ((num /= radix) != 0); \
533
if (neg) *--s = '-'; \
534
strcpy(buf, s); \
535
}
536
537
TYPE_TO_A_FUNC(postoa, POSITION)
538
TYPE_TO_A_FUNC(linenumtoa, LINENUM)
539
TYPE_TO_A_FUNC(inttoa, int)
540
541
/*
542
* Convert a string to an integral type. Return ((type) -1) on overflow.
543
*/
544
#define STR_TO_TYPE_FUNC(funcname, cfuncname, type) \
545
type cfuncname(constant char *buf, constant char **ebuf, int radix) \
546
{ \
547
type val = 0; \
548
lbool v = 0; \
549
for (;; buf++) { \
550
char c = *buf; \
551
int digit = (c >= '0' && c <= '9') ? c - '0' : (c >= 'a' && c <= 'f') ? c - 'a' + 10 : (c >= 'A' && c <= 'F') ? c - 'A' + 10 : -1; \
552
if (digit < 0 || digit >= radix) break; \
553
v = v || ckd_mul(&val, val, radix); \
554
v = v || ckd_add(&val, val, digit); \
555
} \
556
if (ebuf != NULL) *ebuf = buf; \
557
return v ? (type)(-1) : val; \
558
} \
559
type funcname(char *buf, char **ebuf, int radix) \
560
{ \
561
constant char *cbuf = buf; \
562
type r = cfuncname(cbuf, &cbuf, radix); \
563
if (ebuf != NULL) *ebuf = (char *) cbuf; /*{{const-issue}}*/ \
564
return r; \
565
}
566
567
STR_TO_TYPE_FUNC(lstrtopos, lstrtoposc, POSITION)
568
STR_TO_TYPE_FUNC(lstrtoi, lstrtoic, int)
569
STR_TO_TYPE_FUNC(lstrtoul, lstrtoulc, unsigned long)
570
571
/*
572
* Print an integral type.
573
*/
574
#define IPRINT_FUNC(funcname, type, typetoa) \
575
static int funcname(type num, int radix) \
576
{ \
577
char buf[INT_STRLEN_BOUND(num)]; \
578
typetoa(num, buf, radix); \
579
putstr(buf); \
580
return (int) strlen(buf); \
581
}
582
583
IPRINT_FUNC(iprint_int, int, inttoa)
584
IPRINT_FUNC(iprint_linenum, LINENUM, linenumtoa)
585
586
/*
587
* This function implements printf-like functionality
588
* using a more portable argument list mechanism than printf's.
589
*
590
* {{ This paranoia about the portability of printf dates from experiences
591
* with systems in the 1980s and is of course no longer necessary. }}
592
*/
593
public int less_printf(constant char *fmt, PARG *parg)
594
{
595
constant char *s;
596
constant char *es;
597
int col;
598
599
col = 0;
600
while (*fmt != '\0')
601
{
602
if (*fmt != '%')
603
{
604
putchr(*fmt++);
605
col++;
606
} else
607
{
608
++fmt;
609
switch (*fmt++)
610
{
611
case 's':
612
s = parg->p_string;
613
es = s + strlen(s);
614
parg++;
615
while (*s != '\0')
616
{
617
LWCHAR ch = step_charc(&s, +1, es);
618
constant char *ps = utf_mode ? prutfchar(ch) : prchar(ch);
619
while (*ps != '\0')
620
{
621
putchr(*ps++);
622
col++;
623
}
624
}
625
break;
626
case 'd':
627
col += iprint_int(parg->p_int, 10);
628
parg++;
629
break;
630
case 'x':
631
col += iprint_int(parg->p_int, 16);
632
parg++;
633
break;
634
case 'n':
635
col += iprint_linenum(parg->p_linenum, 10);
636
parg++;
637
break;
638
case 'c':
639
s = prchar((LWCHAR) parg->p_char);
640
parg++;
641
while (*s != '\0')
642
{
643
putchr(*s++);
644
col++;
645
}
646
break;
647
case '%':
648
putchr('%');
649
break;
650
}
651
}
652
}
653
return (col);
654
}
655
656
/*
657
* Get a RETURN.
658
* If some other non-trivial char is pressed, unget it, so it will
659
* become the next command.
660
*/
661
public void get_return(void)
662
{
663
int c;
664
665
#if ONLY_RETURN
666
while ((c = getchr()) != '\n' && c != '\r')
667
bell();
668
#else
669
c = getchr();
670
if (c != '\n' && c != '\r' && c != ' ' && c != READ_INTR)
671
ungetcc((char) c);
672
#endif
673
}
674
675
/*
676
* Output a message in the lower left corner of the screen
677
* and wait for carriage return.
678
*/
679
public void error(constant char *fmt, PARG *parg)
680
{
681
int col = 0;
682
static char return_to_continue[] = " (press RETURN)";
683
684
errmsgs++;
685
686
if (!interactive())
687
{
688
less_printf(fmt, parg);
689
putchr('\n');
690
return;
691
}
692
693
if (!oldbot)
694
squish_check();
695
at_exit();
696
clear_bot();
697
at_enter(AT_STANDOUT|AT_COLOR_ERROR);
698
col += so_s_width;
699
col += less_printf(fmt, parg);
700
putstr(return_to_continue);
701
at_exit();
702
col += (int) sizeof(return_to_continue) + so_e_width;
703
704
get_return();
705
lower_left();
706
clear_eol();
707
708
if (col >= sc_width)
709
/*
710
* Printing the message has probably scrolled the screen.
711
* {{ Unless the terminal doesn't have auto margins,
712
* in which case we just hammered on the right margin. }}
713
*/
714
screen_trashed();
715
716
flush();
717
}
718
719
/*
720
* Output a message in the lower left corner of the screen
721
* and don't wait for carriage return.
722
* Usually used to warn that we are beginning a potentially
723
* time-consuming operation.
724
*/
725
static void ierror_suffix(constant char *fmt, PARG *parg, constant char *suffix1, constant char *suffix2, constant char *suffix3)
726
{
727
at_exit();
728
clear_bot();
729
at_enter(AT_STANDOUT|AT_COLOR_ERROR);
730
(void) less_printf(fmt, parg);
731
putstr(suffix1);
732
putstr(suffix2);
733
putstr(suffix3);
734
at_exit();
735
flush();
736
need_clr = 1;
737
}
738
739
public void ierror(constant char *fmt, PARG *parg)
740
{
741
ierror_suffix(fmt, parg, "... (interrupt to abort)", "", "");
742
}
743
744
public void ixerror(constant char *fmt, PARG *parg)
745
{
746
if (!supports_ctrl_x())
747
ierror(fmt, parg);
748
else
749
{
750
char ichar[MAX_PRCHAR_LEN+1];
751
strcpy(ichar, prchar((LWCHAR) intr_char));
752
ierror_suffix(fmt, parg, "... (", ichar, " or interrupt to abort)");
753
}
754
}
755
756
/*
757
* Output a message in the lower left corner of the screen
758
* and return a single-character response.
759
*/
760
public int query(constant char *fmt, PARG *parg)
761
{
762
int c;
763
int col = 0;
764
765
if (interactive())
766
clear_bot();
767
768
(void) less_printf(fmt, parg);
769
c = getchr();
770
771
if (interactive())
772
{
773
lower_left();
774
if (col >= sc_width)
775
screen_trashed();
776
flush();
777
} else
778
{
779
putchr('\n');
780
}
781
782
if (c == 'Q')
783
quit(QUIT_OK);
784
return (c);
785
}
786
787