xref: /freebsd/contrib/less/line.c (revision fa0dc4f0f96a1b77d4be7bcdbf965897cda14521)
1 /*
2  * Copyright (C) 1984-2026  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  * Routines to manipulate the "line buffer".
12  * The line buffer holds a line of output as it is being built
13  * in preparation for output to the screen.
14  */
15 
16 #include "less.h"
17 #include "charset.h"
18 #include "position.h"
19 
20 #if MSDOS_COMPILER==WIN32C
21 #define WIN32_LEAN_AND_MEAN
22 #include <windows.h>
23 #endif
24 
25 #define MAX_PFX_WIDTH (MAX_LINENUM_WIDTH + MAX_STATUSCOL_WIDTH + 1)
26 static struct {
27 	char *buf;    /* Buffer which holds the current output line */
28 	int *attr;    /* Parallel to buf, to hold attributes */
29 	size_t print; /* Index in buf of first printable char */
30 	size_t end;   /* Number of chars in buf */
31 	size_t prev_end; /* Number of chars in buf for previous line */
32 	char pfx[MAX_PFX_WIDTH]; /* Holds status column and line number */
33 	int pfx_attr[MAX_PFX_WIDTH];
34 	size_t pfx_end;  /* Number of chars in pfx */
35 } linebuf;
36 
37 /*
38  * Buffer of ansi sequences which have been shifted off the left edge
39  * of the screen.
40  */
41 static struct xbuffer shifted_ansi;
42 
43 /*
44  * Ring buffer of last ansi sequences sent.
45  * While sending a line, these will be resent at the end
46  * of any highlighted string, to restore text modes.
47  * {{ Not ideal, since we don't really know how many to resend. }}
48  */
49 #define NUM_LAST_ANSIS 3
50 static struct xbuffer last_ansi;
51 static struct xbuffer last_ansis[NUM_LAST_ANSIS];
52 static int curr_last_ansi;
53 
54 static size_t size_linebuf = 0; /* Size of line buffer (and attr buffer) */
55 static struct ansi_state *line_ansi = NULL;
56 static lbool ansi_in_line;
57 static int ff_starts_line;
58 static lbool hlink_in_line;
59 static int line_mark_attr;
60 static int cshift;   /* Current left-shift of output line buffer */
61 public int hshift;   /* Desired left-shift of output line buffer */
62 public int tabstops[TABSTOP_MAX] = { 0 }; /* Custom tabstops */
63 public int ntabstops = 1;        /* Number of tabstops */
64 public int tabdefault = 8;       /* Default repeated tabstops */
65 public POSITION highest_hilite;  /* Pos of last hilite in file found so far */
66 static POSITION line_pos;
67 static POSITION line_contig_pos = NULL_POSITION; /* One after last byte processed */
68 
69 static int end_column;  /* Printable length, accounting for backspaces, etc. */
70 static int right_curr;
71 static int right_column;
72 static int overstrike;  /* Next char should overstrike previous char */
73 static int last_overstrike = AT_NORMAL;
74 static lbool is_null_line;  /* There is no current line */
75 static LWCHAR pendc;
76 static POSITION pendpos;
77 static constant char *end_ansi_chars;
78 static constant char *mid_ansi_chars;
79 static constant char *osc_ansi_chars;
80 static int osc_ansi_allow_count;
81 static long *osc_ansi_allow;
82 static lbool in_hilite;
83 static lbool clear_after_line;
84 
85 static int attr_swidth(int a);
86 static int attr_ewidth(int a);
87 static int do_append(LWCHAR ch, constant char *rep, POSITION pos);
88 
89 extern int sigs;
90 extern int bs_mode;
91 extern int proc_backspace;
92 extern int proc_tab;
93 extern int proc_return;
94 extern int linenums;
95 extern int ctldisp;
96 extern int twiddle;
97 extern int status_col;
98 extern int status_col_width;
99 extern int linenum_width;
100 extern int auto_wrap, defer_wrap;
101 extern int bo_s_width, bo_e_width;
102 extern int ul_s_width, ul_e_width;
103 extern int bl_s_width, bl_e_width;
104 extern int so_s_width, so_e_width;
105 extern int sc_width, sc_height;
106 extern int utf_mode;
107 extern POSITION start_attnpos;
108 extern POSITION end_attnpos;
109 extern LWCHAR rscroll_char;
110 extern int rscroll_attr;
111 extern int use_color;
112 extern int status_line;
113 
114 static char mbc_buf[MAX_UTF_CHAR_LEN];
115 static int mbc_buf_len = 0;
116 static int mbc_buf_index = 0;
117 static POSITION mbc_pos;
118 static size_t saved_line_end;
119 static int saved_end_column;
120 
121 /* Configurable color map */
122 struct color_map { int attr; char color[12]; };
123 static struct color_map color_map[] = {
124 	{ AT_UNDERLINE,            "" },
125 	{ AT_BOLD,                 "" },
126 	{ AT_BLINK,                "" },
127 	{ AT_STANDOUT,             "" },
128 	{ AT_COLOR_ATTN,           "Wm" },
129 	{ AT_COLOR_BIN,            "kR" },
130 	{ AT_COLOR_CTRL,           "kR" },
131 	{ AT_COLOR_ERROR,          "kY" },
132 	{ AT_COLOR_LINENUM,        "c*" },
133 	{ AT_COLOR_MARK,           "Wb" },
134 	{ AT_COLOR_PROMPT,         "kC" },
135 	{ AT_COLOR_RSCROLL,        "kc" },
136 	{ AT_COLOR_HEADER,         "" },
137 	{ AT_COLOR_SEARCH,         "kG" },
138 	{ AT_COLOR_TILDE,          "-d" },
139 	{ AT_COLOR_TARGET,         "-u" },
140 	{ AT_COLOR_SUBSEARCH(1),   "ky" },
141 	{ AT_COLOR_SUBSEARCH(2),   "wb" },
142 	{ AT_COLOR_SUBSEARCH(3),   "YM" },
143 	{ AT_COLOR_SUBSEARCH(4),   "Yr" },
144 	{ AT_COLOR_SUBSEARCH(5),   "Wc" },
145 };
146 
147 /* State while processing an ANSI escape sequence */
148 struct ansi_state {
149 	osc8_state ostate; /* State while processing OSC8 sequence */
150 	unsigned int otype; /* OSC type number */
151 	unsigned int escs_in_seq;
152 };
153 
154 /*
155  * Initialize from environment variables.
156  */
init_line(void)157 public void init_line(void)
158 {
159 	int ax;
160 	constant char *s;
161 
162 	end_ansi_chars = lgetenv("LESSANSIENDCHARS");
163 	if (isnullenv(end_ansi_chars))
164 		end_ansi_chars = "m";
165 
166 	mid_ansi_chars = lgetenv("LESSANSIMIDCHARS");
167 	if (isnullenv(mid_ansi_chars))
168 		mid_ansi_chars = "0123456789:;[?!\"'#%()*+ ";
169 
170 	osc_ansi_chars = lgetenv("LESSANSIOSCCHARS");
171 	if (isnullenv(osc_ansi_chars))
172 		osc_ansi_chars = "";
173 
174 	osc_ansi_allow_count = 0;
175 	s = lgetenv("LESSANSIOSCALLOW");
176 	if (!isnullenv(s))
177 	{
178 		struct xbuffer xbuf;
179 		xbuf_init(&xbuf);
180 		for (;;)
181 		{
182 			long num;
183 			s = skipspc(s);
184 			if (*s == '\0')
185 				break;
186 			num = lstrtoulc(s, &s, 10);
187 			s = skipspc(s);
188 			if (*s == ',')
189 				++s;
190 			xbuf_add_data(&xbuf, &num, sizeof(num));
191 			++osc_ansi_allow_count;
192 		}
193 		osc_ansi_allow = (long *) xbuf.data;
194 	}
195 
196 	linebuf.buf = (char *) ecalloc(LINEBUF_SIZE, sizeof(char));
197 	linebuf.attr = (int *) ecalloc(LINEBUF_SIZE, sizeof(int));
198 	size_linebuf = LINEBUF_SIZE;
199 	xbuf_init(&shifted_ansi);
200 	xbuf_init(&last_ansi);
201 	for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
202 		xbuf_init(&last_ansis[ax]);
203 	curr_last_ansi = 0;
204 }
205 
206 /*
207  * Expand the line buffer.
208  */
expand_linebuf(void)209 static int expand_linebuf(void)
210 {
211 	/* Double the size of the line buffer. */
212 	size_t new_size = size_linebuf * 2;
213 	char *new_buf = (char *) calloc(new_size, sizeof(char));
214 	int *new_attr = (int *) calloc(new_size, sizeof(int));
215 	if (new_buf == NULL || new_attr == NULL)
216 	{
217 		if (new_attr != NULL)
218 			free(new_attr);
219 		if (new_buf != NULL)
220 			free(new_buf);
221 		return 1;
222 	}
223 	/*
224 	 * We just calloc'd the buffers; copy the old contents.
225 	 */
226 	memcpy(new_buf, linebuf.buf, size_linebuf * sizeof(char));
227 	memcpy(new_attr, linebuf.attr, size_linebuf * sizeof(int));
228 	free(linebuf.attr);
229 	free(linebuf.buf);
230 	linebuf.buf = new_buf;
231 	linebuf.attr = new_attr;
232 	size_linebuf = new_size;
233 	return 0;
234 }
235 
236 /*
237  * Is a character ASCII?
238  */
is_ascii_char(LWCHAR ch)239 public lbool is_ascii_char(LWCHAR ch)
240 {
241 	return (ch <= 0x7F);
242 }
243 
244 /*
245  */
inc_end_column(int w)246 static void inc_end_column(int w)
247 {
248 	if (end_column > right_column && w > 0)
249 	{
250 		right_column = end_column;
251 		right_curr = (int) linebuf.end;
252 	}
253 	end_column += w;
254 }
255 
line_position(void)256 public POSITION line_position(void)
257 {
258 	return line_pos;
259 }
260 
261 /*
262  * Is this byte the next one after the previous byte processed?
263  */
is_line_contig_pos(POSITION pos)264 public lbool is_line_contig_pos(POSITION pos)
265 {
266 	return pos == line_contig_pos;
267 }
268 
269 /*
270  * Set the position of the next byte to be processed.
271  */
set_line_contig_pos(POSITION pos)272 public void set_line_contig_pos(POSITION pos)
273 {
274 	line_contig_pos = pos;
275 }
276 
277 /*
278  * Copy any ANSI sequences from line buffer to shifted_ansi.
279  */
pshift(size_t end)280 static void pshift(size_t end)
281 {
282 	size_t i;
283 	for (i = linebuf.print;  i < end;  i++)
284 		if (linebuf.attr[i] == AT_ANSI)
285 			xbuf_add_char(&shifted_ansi, linebuf.buf[i]);
286 }
287 
288 /*
289  * Rewind the line buffer.
290  */
prewind(lbool contig)291 public void prewind(lbool contig)
292 {
293 	int ax;
294 
295 	xbuf_reset(&shifted_ansi);
296 	if (contig && linebuf.prev_end != 0)
297 		pshift(linebuf.prev_end);
298 	linebuf.print = 6; /* big enough for longest UTF-8 sequence */
299 	linebuf.pfx_end = 0;
300 	for (linebuf.end = 0; linebuf.end < linebuf.print; linebuf.end++)
301 	{
302 		linebuf.buf[linebuf.end] = '\0';
303 		linebuf.attr[linebuf.end] = 0;
304 	}
305 
306 	end_column = 0;
307 	right_curr = 0;
308 	right_column = 0;
309 	cshift = 0;
310 	overstrike = 0;
311 	last_overstrike = AT_NORMAL;
312 	mbc_buf_len = 0;
313 	is_null_line = FALSE;
314 	pendc = '\0';
315 	in_hilite = FALSE;
316 	ansi_in_line = FALSE;
317 	ff_starts_line = -1;
318 	hlink_in_line = FALSE;
319 	clear_after_line = FALSE;
320 	line_mark_attr = 0;
321 	line_pos = NULL_POSITION;
322 	xbuf_reset(&last_ansi);
323 	for (ax = 0;  ax < NUM_LAST_ANSIS;  ax++)
324 		xbuf_reset(&last_ansis[ax]);
325 	curr_last_ansi = 0;
326 }
327 
328 /*
329  * Set a character in the line buffer.
330  */
set_linebuf(size_t n,char ch,int attr)331 static void set_linebuf(size_t n, char ch, int attr)
332 {
333 	if (n >= size_linebuf)
334 	{
335 		/*
336 		 * Won't fit in line buffer.
337 		 * Try to expand it.
338 		 */
339 		if (expand_linebuf())
340 			return;
341 	}
342 	linebuf.buf[n] = ch;
343 	linebuf.attr[n] = attr;
344 }
345 
346 /*
347  * Append a character to the line buffer.
348  */
add_linebuf(char ch,int attr,int w)349 static void add_linebuf(char ch, int attr, int w)
350 {
351 	set_linebuf(linebuf.end++, ch, attr);
352 	inc_end_column(w);
353 }
354 
355 /*
356  * Append a string to the line buffer.
357  */
addstr_linebuf(constant char * s,int attr,int cw)358 static void addstr_linebuf(constant char *s, int attr, int cw)
359 {
360 	for ( ;  *s != '\0';  s++)
361 		add_linebuf(*s, attr, cw);
362 }
363 
364 /*
365  * Set a character in the line prefix buffer.
366  */
set_pfx(size_t n,char ch,int attr)367 static void set_pfx(size_t n, char ch, int attr)
368 {
369 	linebuf.pfx[n] = ch;
370 	linebuf.pfx_attr[n] = attr;
371 }
372 
373 /*
374  * Append a character to the line prefix buffer.
375  */
add_pfx(char ch,int attr)376 static void add_pfx(char ch, int attr)
377 {
378 	set_pfx(linebuf.pfx_end++, ch, attr);
379 }
380 
381 /*
382  * Insert the status column and line number into the line buffer.
383  */
plinestart(POSITION pos)384 public void plinestart(POSITION pos)
385 {
386 	LINENUM linenum = 0;
387 
388 	if (linenums == OPT_ONPLUS)
389 	{
390 		/*
391 		 * Get the line number and put it in the current line.
392 		 * {{ Note: since find_linenum calls forw_raw_line,
393 		 *    it may seek in the input file, requiring the caller
394 		 *    of plinestart to re-seek if necessary. }}
395 		 * {{ Since forw_raw_line modifies linebuf, we must
396 		 *    do this first, before storing anything in linebuf. }}
397 		 */
398 		linenum = find_linenum(pos);
399 	}
400 
401 	/*
402 	 * Display a status column if the -J option is set.
403 	 */
404 	if (status_col || status_line)
405 	{
406 		char c = posmark(pos);
407 		if (c != 0)
408 			line_mark_attr = AT_HILITE|AT_COLOR_MARK;
409 		else if (start_attnpos != NULL_POSITION &&
410 		         pos >= start_attnpos && pos <= end_attnpos)
411 			line_mark_attr = AT_HILITE|AT_COLOR_ATTN;
412 		if (status_col)
413 		{
414 			add_pfx(c ? c : ' ', line_mark_attr); /* column 0: status */
415 			while (linebuf.pfx_end < (size_t) status_col_width) /*{{type-issue}}*/
416 				add_pfx(' ', AT_NORMAL);
417 		}
418 	}
419 
420 	/*
421 	 * Display the line number at the start of each line
422 	 * if the -N option is set.
423 	 */
424 	if (linenums == OPT_ONPLUS)
425 	{
426 		char buf[INT_STRLEN_BOUND(linenum) + 2];
427 		size_t len;
428 		size_t i;
429 
430 		linenum = vlinenum(linenum);
431 		if (linenum == 0)
432 			len = 0;
433 		else
434 		{
435 			linenumtoa(linenum, buf, 10);
436 			len = strlen(buf);
437 		}
438 		for (i = 0; i + len < (size_t) linenum_width; i++)
439 			add_pfx(' ', AT_NORMAL);
440 		for (i = 0; i < len; i++)
441 			add_pfx(buf[i], use_color ? AT_COLOR_LINENUM : AT_BOLD);
442 		add_pfx(' ', AT_NORMAL);
443 	}
444 	end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/
445 }
446 
447 /*
448  * Return the width of the line prefix (status column and line number).
449  * {{ Actual line number can be wider than linenum_width. }}
450  */
line_pfx_width(void)451 public unsigned line_pfx_width(void)
452 {
453 	int width = 0;
454 	if (status_col)
455 		width += status_col_width;
456 	if (linenums == OPT_ONPLUS)
457 		width += linenum_width + 1;
458 	return width;
459 }
460 
461 /*
462  * Shift line left so that the last char is just to the left
463  * of the first visible column.
464  */
pshift_all(void)465 public void pshift_all(void)
466 {
467 	pshift(linebuf.end);
468 	linebuf.end = linebuf.print;
469 	end_column = (int) linebuf.pfx_end; /*{{type-issue}}*/
470 	line_pos = NULL_POSITION;
471 }
472 
473 /*
474  * Return the printing width of the start (enter) sequence
475  * for a given character attribute.
476  */
attr_swidth(int a)477 static int attr_swidth(int a)
478 {
479 	int w = 0;
480 
481 	a = apply_at_specials(a);
482 
483 	if (a & AT_UNDERLINE)
484 		w += ul_s_width;
485 	if (a & AT_BOLD)
486 		w += bo_s_width;
487 	if (a & AT_BLINK)
488 		w += bl_s_width;
489 	if (a & AT_STANDOUT)
490 		w += so_s_width;
491 
492 	return w;
493 }
494 
495 /*
496  * Return the printing width of the end (exit) sequence
497  * for a given character attribute.
498  */
attr_ewidth(int a)499 static int attr_ewidth(int a)
500 {
501 	int w = 0;
502 
503 	a = apply_at_specials(a);
504 
505 	if (a & AT_UNDERLINE)
506 		w += ul_e_width;
507 	if (a & AT_BOLD)
508 		w += bo_e_width;
509 	if (a & AT_BLINK)
510 		w += bl_e_width;
511 	if (a & AT_STANDOUT)
512 		w += so_e_width;
513 
514 	return w;
515 }
516 
517 /*
518  * Return the printing width of a given character and attribute,
519  * if the character were added after prev_ch.
520  * Adding a character with a given attribute may cause an enter or exit
521  * attribute sequence to be inserted, so this must be taken into account.
522  */
pwidth(LWCHAR ch,int a,LWCHAR prev_ch,int prev_a)523 public int pwidth(LWCHAR ch, int a, LWCHAR prev_ch, int prev_a)
524 {
525 	int w;
526 
527 	if (ch == '\b')
528 	{
529 		/*
530 		 * Backspace moves backwards one or two positions.
531 		 */
532 		if (prev_a & (AT_ANSI|AT_BINARY))
533 			return (int) strlen(prchar('\b')); /*{{type-issue}}*/
534 		return (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
535 	}
536 
537 	if (!utf_mode || is_ascii_char(ch))
538 	{
539 		if (control_char(ch))
540 		{
541 			/*
542 			 * Control characters do unpredictable things,
543 			 * so we don't even try to guess; say it doesn't move.
544 			 * This can only happen if the -r flag is in effect.
545 			 */
546 			return (0);
547 		}
548 	} else
549 	{
550 		if (ch == VARSEL_15)
551 			/* If prev char was double width, make it single width. */
552 			return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 2) ? -1 : 0;
553 		if (ch == VARSEL_16)
554 			/* If prev char was single width, make it double width. */
555 			return (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1) ? +1 : 0;
556 		if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
557 		{
558 			/*
559 			 * Composing and combining chars take up no space.
560 			 *
561 			 * Some terminals, upon failure to compose a
562 			 * composing character with the character(s) that
563 			 * precede(s) it will actually take up one end_column
564 			 * for the composing character; there isn't much
565 			 * we could do short of testing the (complex)
566 			 * composition process ourselves and printing
567 			 * a binary representation when it fails.
568 			 */
569 			return (0);
570 		}
571 	}
572 
573 	/*
574 	 * Other characters take one or two columns,
575 	 * plus the width of any attribute enter/exit sequence.
576 	 */
577 	w = 1;
578 	if (is_wide_char(ch))
579 		w++;
580 	if (linebuf.end > 0 && !is_at_equiv(linebuf.attr[linebuf.end-1], a))
581 		w += attr_ewidth(linebuf.attr[linebuf.end-1]);
582 	if (apply_at_specials(a) != AT_NORMAL &&
583 	    (linebuf.end == 0 || !is_at_equiv(linebuf.attr[linebuf.end-1], a)))
584 		w += attr_swidth(a);
585 	return (w);
586 }
587 
588 /*
589  * Delete to the previous base character in the line buffer.
590  */
backc(void)591 static int backc(void)
592 {
593 	LWCHAR ch;
594 	char *p;
595 
596 	if (linebuf.end == 0)
597 		return (0);
598 	p = &linebuf.buf[linebuf.end];
599 	ch = step_char(&p, -1, linebuf.buf);
600 	/* Skip back to the next nonzero-width char. */
601 	while (p > linebuf.buf)
602 	{
603 		LWCHAR prev_ch;
604 		int width;
605 		linebuf.end = ptr_diff(p, linebuf.buf);
606 		prev_ch = step_char(&p, -1, linebuf.buf);
607 		width = pwidth(ch, linebuf.attr[linebuf.end], prev_ch, linebuf.attr[linebuf.end-1]);
608 		end_column -= width;
609 		/* {{ right_column? }} */
610 		if (width > 0)
611 			break;
612 		ch = prev_ch;
613 	}
614 	return (1);
615 }
616 
617 /*
618  * Preserve the current position in the line buffer (for word wrapping).
619  */
savec(void)620 public void savec(void)
621 {
622 	saved_line_end = linebuf.end;
623 	saved_end_column = end_column;
624 }
625 
626 /*
627  * Restore the position in the line buffer (start of line for word wrapping).
628  */
loadc(void)629 public void loadc(void)
630 {
631 	linebuf.end = saved_line_end;
632 	end_column = saved_end_column;
633 }
634 
635 /*
636  * Is a character the end of an ANSI escape sequence?
637  */
is_ansi_end(LWCHAR ch)638 public lbool is_ansi_end(LWCHAR ch)
639 {
640 	if (!is_ascii_char(ch))
641 		return (FALSE);
642 	return (ch != 0 && strchr(end_ansi_chars, (char) ch) != NULL);
643 }
644 
645 /*
646  * Can a char appear in an ANSI escape sequence, before the end char?
647  */
is_ansi_middle(LWCHAR ch)648 public lbool is_ansi_middle(LWCHAR ch)
649 {
650 	if (!is_ascii_char(ch))
651 		return (FALSE);
652 	if (is_ansi_end(ch))
653 		return (FALSE);
654 	return (ch != 0 && strchr(mid_ansi_chars, (char) ch) != NULL);
655 }
656 
657 /*
658  * Skip past an ANSI escape sequence.
659  * pp is initially positioned just after the CSI_START char.
660  */
skip_ansi(struct ansi_state * pansi,LWCHAR ch,constant char ** pp,constant char * limit)661 public void skip_ansi(struct ansi_state *pansi, LWCHAR ch, constant char **pp, constant char *limit)
662 {
663 	ansi_step(pansi, ch);
664 	do {
665 		ch = step_charc(pp, +1, limit);
666 	} while (*pp < limit && ansi_step(pansi, ch) == ANSI_MID);
667 	/* Note that we discard final char, for which is_ansi_end is true. */
668 }
669 
670 /*
671  * Determine if a character starts an ANSI escape sequence.
672  * If so, return an ansi_state struct; otherwise return NULL.
673  */
ansi_start(LWCHAR ch)674 public struct ansi_state * ansi_start(LWCHAR ch)
675 {
676 	struct ansi_state *pansi;
677 
678 	if (!IS_CSI_START(ch))
679 		return NULL;
680 	pansi = ecalloc(1, sizeof(struct ansi_state));
681 	pansi->ostate = OSC_START;
682 	pansi->otype = 0;
683 	pansi->escs_in_seq = 0;
684 	return pansi;
685 }
686 
687 /*
688  * Is a character a valid intro char for an OSC sequence?
689  * An intro char is the one immediately after the ESC, usually ']'.
690  */
valid_osc_intro(char ch,lbool content)691 static lbool valid_osc_intro(char ch, lbool content)
692 {
693 	constant char *p = strchr(osc_ansi_chars, ch);
694 	if (p == NULL)
695 		return FALSE;
696 	return (!content || p[1] == '*');
697 }
698 
699 /*
700  * Is a given number a valid OSC type?
701  */
valid_osc_type(int otype,lbool content)702 static lbool valid_osc_type(int otype, lbool content)
703 {
704 	int i;
705 	if (!content)
706 		return TRUE;
707 	if (otype == 8)
708 		return TRUE;
709 	for (i = 0;  i < osc_ansi_allow_count;  i++)
710 		if (osc_ansi_allow[i] == otype)
711 			return TRUE;
712 	return FALSE;
713 }
714 
715 /*
716  * Helper function for ansi_step.
717  */
osc_return(struct ansi_state * pansi,osc8_state ostate,ansi_state astate)718 static ansi_state osc_return(struct ansi_state *pansi, osc8_state ostate, ansi_state astate)
719 {
720 	pansi->ostate = ostate;
721 	return astate;
722 }
723 
724 /*
725  * Determine whether the next char in an ANSI escape sequence
726  * ends the sequence.
727  */
ansi_step2(struct ansi_state * pansi,LWCHAR ch,lbool content)728 static ansi_state ansi_step2(struct ansi_state *pansi, LWCHAR ch, lbool content)
729 {
730 	/*
731 	 * Pass thru OS commands. Assume OSC commands do not move the cursor.
732 	 * A "typed" OSC starts with ESC ] <integer> <semicolon>, followed by an
733 	 * arbitrary string, and ends with a String Terminator (ESC-backslash or BEL).
734 	 * An untyped OSC starts with ESC ] or ESC x where x is in osc_ansi_chars,
735 	 * and ends with ST.
736 	 * The only typed OSC we actually parse is OSC 8.
737 	 */
738 	switch (pansi->ostate)
739 	{
740 	case OSC_START:
741 		if (IS_CSI_START(ch))
742 			return osc_return(pansi, OSC_INTRO, ANSI_MID);
743 		break;
744 	case OSC_INTRO:
745 		if (ch == ']')
746 			return osc_return(pansi, OSC_TYPENUM, ANSI_MID);
747 		if (is_ascii_char(ch) && valid_osc_intro((char) ch, content))
748 			return osc_return(pansi, OSC_STRING, ANSI_MID);
749 		if (IS_CSI_START(ch))
750 			return osc_return(pansi, OSC_INTRO, ANSI_MID);
751 		/* ESC not followed by bracket; restart. */
752 		pansi->ostate = OSC_START;
753 		break;
754 	case OSC_TYPENUM:
755 		if (ch >= '0' && ch <= '9')
756 		{
757 			if (ckd_mul(&pansi->otype, pansi->otype, 10) ||
758 			    ckd_add(&pansi->otype, pansi->otype, ch - '0'))
759 				return osc_return(pansi, OSC_STRING, ANSI_MID);
760 			return osc_return(pansi, OSC_TYPENUM, ANSI_MID);
761 		}
762 		if (ch == ';')
763 			return osc_return(pansi, (pansi->otype == 8) ? OSC8_PARAMS : OSC_STRING, ANSI_MID);
764 		/* OSC is untyped */
765 		if (IS_CSI_START(ch))
766 			return osc_return(pansi, OSC_END_CSI, ANSI_MID);
767 		if (ch == '\7')
768 			return osc_return(pansi, OSC_END, ANSI_END);
769 		return osc_return(pansi, OSC_STRING, ANSI_MID);
770 	case OSC8_PARAMS:
771 		if (ch == ';')
772 			return osc_return(pansi, OSC8_URI, ANSI_MID);
773 		/* FALLTHRU */
774 	case OSC8_URI:
775 	case OSC_STRING:
776 		/* Look for ST. */
777 		if (ch == '\7')
778 			return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR);
779 		if (IS_CSI_START(ch))
780 		{
781 			pansi->escs_in_seq++;
782 			return osc_return(pansi, OSC_END_CSI, ANSI_MID);
783 		}
784 		/* Stay in same ostate */
785 		return ANSI_MID;
786 	case OSC_END_CSI:
787 		/* Got ESC of ST, expect backslash next. */
788 		if (ch == '\\')
789 			return osc_return(pansi, OSC_END, valid_osc_type(pansi->otype, content) ? ANSI_END : ANSI_ERR);
790 		/* ESC not followed by backslash. */
791 		return osc_return(pansi, OSC_STRING, ANSI_MID);
792 	case OSC_END:
793 		return ANSI_END;
794 	case OSC8_NOT:
795 		/* cannot happen */
796 		break;
797 	}
798 	/* Check for SGR sequences */
799 	if (is_ansi_middle(ch))
800 		return ANSI_MID;
801 	if (is_ansi_end(ch))
802 		return ANSI_END;
803 	return ANSI_ERR;
804 }
805 
ansi_step(struct ansi_state * pansi,LWCHAR ch)806 public ansi_state ansi_step(struct ansi_state *pansi, LWCHAR ch)
807 {
808 	return ansi_step2(pansi, ch, TRUE);
809 }
810 
811 /*
812  * Return the current OSC8 parsing state.
813  */
ansi_osc8_state(struct ansi_state * pansi)814 public osc8_state ansi_osc8_state(struct ansi_state *pansi)
815 {
816 	return pansi->ostate;
817 }
818 
819 /*
820  * Free an ansi_state structure.
821  */
ansi_done(struct ansi_state * pansi)822 public void ansi_done(struct ansi_state *pansi)
823 {
824 	free(pansi);
825 }
826 
827 /*
828  * Will w characters in attribute a fit on the screen?
829  */
fits_on_screen(int w,int a)830 static lbool fits_on_screen(int w, int a)
831 {
832 	if (ctldisp == OPT_ON)
833 		/* We're not counting, so say that everything fits. */
834 		return TRUE;
835 	return (end_column - cshift + w + attr_ewidth(a) <= sc_width);
836 }
837 
838 /*
839  * Append a character and attribute to the line buffer.
840  */
841 #define STORE_CHAR(ch,a,rep,pos) \
842 	do { \
843 		if (store_char((ch),(a),(rep),(pos))) return (1); \
844 	} while (0)
845 
store_char(LWCHAR ch,int a,constant char * rep,POSITION pos)846 static int store_char(LWCHAR ch, int a, constant char *rep, POSITION pos)
847 {
848 	int w;
849 	size_t i;
850 	size_t replen;
851 	char cs;
852 	int ov;
853 	lbool need_shift;
854 
855 	ov = (a & (AT_UNDERLINE|AT_BOLD));
856 	if (ov != AT_NORMAL)
857 		last_overstrike = ov;
858 
859 #if HILITE_SEARCH
860 	{
861 		int matches;
862 		int resend_last = 0;
863 		int hl_attr = 0;
864 
865 		if (pos != NULL_POSITION && a != AT_ANSI)
866 		{
867 			hl_attr = is_hilited_attr(pos, pos+1, 0, &matches);
868 			if (hl_attr == 0 && status_line)
869 				hl_attr = line_mark_attr;
870 		}
871 		if (hl_attr)
872 		{
873 			/*
874 			 * This character should be highlighted.
875 			 * Override the attribute passed in.
876 			 */
877 			a |= hl_attr;
878 			if (highest_hilite != NULL_POSITION && pos != NULL_POSITION && pos > highest_hilite)
879 				highest_hilite = pos;
880 			in_hilite = TRUE;
881 		} else
882 		{
883 			if (in_hilite)
884 			{
885 				/*
886 				 * This is the first non-hilited char after a hilite.
887 				 * Resend the last ANSI seq to restore color.
888 				 */
889 				resend_last = 1;
890 			}
891 			in_hilite = FALSE;
892 		}
893 		if (resend_last)
894 		{
895 			int ai;
896 			for (ai = 0;  ai < NUM_LAST_ANSIS;  ai++)
897 			{
898 				int ax = (curr_last_ansi + ai) % NUM_LAST_ANSIS;
899 				for (i = 0;  i < last_ansis[ax].end;  i++)
900 					STORE_CHAR(last_ansis[ax].data[i], AT_ANSI, NULL, pos);
901 			}
902 		}
903 	}
904 #endif
905 
906 	if (a == AT_ANSI) {
907 		w = 0;
908 	} else {
909 		char *p = &linebuf.buf[linebuf.end];
910 		LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
911 		int prev_a = (linebuf.end > 0) ? linebuf.attr[linebuf.end-1] : 0;
912 		w = pwidth(ch, a, prev_ch, prev_a);
913 	}
914 
915 	if (!fits_on_screen(w, a))
916 		return (1);
917 
918 	if (rep == NULL)
919 	{
920 		cs = (char) ch;
921 		rep = &cs;
922 		replen = 1;
923 	} else
924 	{
925 		replen = (size_t) utf_len(rep[0]); /*{{type-issue}}*/
926 	}
927 
928 	if (cshift == hshift)
929 	{
930 		if (line_pos == NULL_POSITION)
931 			line_pos = pos;
932 		if (shifted_ansi.end > 0)
933 		{
934 			/* Copy shifted ANSI sequences to beginning of line. */
935 			for (i = 0;  i < shifted_ansi.end;  i++)
936 				add_linebuf((char) shifted_ansi.data[i], AT_ANSI, 0);
937 			xbuf_reset(&shifted_ansi);
938 		}
939 		if (linebuf.end == linebuf.print+1)
940 		{
941 			/* If first char is a placeholder, the one before it is double-width.
942 			 * VS15 changes the double-width char to single-width, so replace the
943 			 * placeholder with this VS15. */
944 			if (ch == VARSEL_15 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER))
945 			{
946 				linebuf.end--;
947 				inc_end_column(-1);
948 			}
949 		} else if (linebuf.end == linebuf.print)
950 		{
951 			/* VS16 changes the previous single-width char to double-width.
952 			 * Add a placeholder to represent the second half of the
953 			 * double-width char. */
954 			if (ch == VARSEL_16)
955 			{
956 				char *p = &linebuf.buf[linebuf.end];
957 				LWCHAR prev_ch = (linebuf.end > 0) ? step_char(&p, -1, linebuf.buf) : 0;
958 				if (prev_ch != 0 && pwidth(prev_ch, a, 0, 0) == 1)
959 					add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0);
960 			}
961 		}
962 	}
963 
964 	/* Add the char to the buf, even if we will left-shift it next. */
965 	need_shift = (cshift < hshift);
966 	if (!need_shift && w <= 0 && linebuf.end <= linebuf.print+1 && is_composing_char(ch) &&
967 		(linebuf.end == linebuf.print || (linebuf.end == linebuf.print+1 && (linebuf.attr[linebuf.end-1] & AT_PLACEHOLDER))))
968 		need_shift = TRUE;
969 	inc_end_column(w);
970 	for (i = 0;  i < replen;  i++)
971 		add_linebuf(*rep++, a, 0);
972 
973 	if (need_shift)
974 	{
975 		/* We haven't left-shifted enough yet. */
976 		if (a == AT_ANSI)
977 			xbuf_add_char(&shifted_ansi, (char) ch); /* Save ANSI attributes */
978 		if (linebuf.end > linebuf.print)
979 		{
980 			/* Shift left enough to put last byte of this char at print-1. */
981 			size_t i;
982 			for (i = 0; i < linebuf.print; i++)
983 			{
984 				linebuf.buf[i] = linebuf.buf[i+replen];
985 				linebuf.attr[i] = linebuf.attr[i+replen];
986 			}
987 			linebuf.end -= replen;
988 			cshift += w;
989 			/*
990 			 * If the char we just left-shifted was double width,
991 			 * the 2 spaces we shifted may be too much.
992 			 * Represent the "half char" at start of line with a highlighted space.
993 			 */
994 			while (cshift > hshift)
995 			{
996 				add_linebuf(' ', rscroll_attr|AT_PLACEHOLDER, 0);
997 				cshift--;
998 			}
999 		}
1000 	}
1001 	return (0);
1002 }
1003 
1004 #define STORE_STRING(s,a,pos) \
1005 	do { if (store_string((s),(a),(pos))) return (1); } while (0)
1006 
store_string(constant char * s,int a,POSITION pos)1007 static int store_string(constant char *s, int a, POSITION pos)
1008 {
1009 	if (!fits_on_screen((int) strlen(s), a))
1010 		return 1;
1011 	for ( ;  *s != 0;  s++)
1012 		STORE_CHAR((LWCHAR)*s, a, NULL, pos);
1013 	return 0;
1014 }
1015 
1016 /*
1017  * Return number of spaces from col to the next tab stop.
1018  */
tab_spaces(int col)1019 static int tab_spaces(int col)
1020 {
1021 	int to_tab = col - (int) linebuf.pfx_end; /*{{type-issue}}*/
1022 
1023 	if (ntabstops < 2 || to_tab >= tabstops[ntabstops-1])
1024 		to_tab = tabdefault -
1025 		     ((to_tab - tabstops[ntabstops-1]) % tabdefault);
1026 	else
1027 	{
1028 		int i;
1029 		for (i = ntabstops - 2;  i >= 0;  i--)
1030 			if (to_tab >= tabstops[i])
1031 				break;
1032 		to_tab = tabstops[i+1] - to_tab;
1033 	}
1034 	return to_tab;
1035 }
1036 
1037 /*
1038  * Append a tab to the line buffer.
1039  * Store spaces to represent the tab.
1040  */
1041 #define STORE_TAB(a,pos) \
1042 	do { if (store_tab((a),(pos))) return (1); } while (0)
1043 
store_tab(int attr,POSITION pos)1044 static int store_tab(int attr, POSITION pos)
1045 {
1046 	int to_tab = tab_spaces(end_column);
1047 	do {
1048 		STORE_CHAR(' ', attr, " ", pos);
1049 	} while (--to_tab > 0);
1050 	return 0;
1051 }
1052 
1053 #define STORE_PRCHAR(c, pos) \
1054 	do { if (store_prchar((c), (pos))) return 1; } while (0)
1055 
store_prchar(LWCHAR c,POSITION pos)1056 static int store_prchar(LWCHAR c, POSITION pos)
1057 {
1058 	/*
1059 	 * Convert to printable representation.
1060 	 */
1061 	STORE_STRING(prchar(c), AT_BINARY|AT_COLOR_CTRL, pos);
1062 	return 0;
1063 }
1064 
flush_mbc_buf(POSITION pos)1065 static int flush_mbc_buf(POSITION pos)
1066 {
1067 	int i;
1068 
1069 	for (i = 0; i < mbc_buf_index; i++)
1070 		if (store_prchar((LWCHAR) mbc_buf[i], pos))
1071 			return mbc_buf_index - i;
1072 	return 0;
1073 }
1074 
1075 /*
1076  * Append a character to the line buffer.
1077  * Expand tabs into spaces, handle underlining, boldfacing, etc.
1078  * Returns 0 if ok, 1 if couldn't fit in buffer.
1079  */
pappend_b(char c,POSITION pos,lbool before_pendc)1080 public int pappend_b(char c, POSITION pos, lbool before_pendc)
1081 {
1082 	LWCHAR ch = (unsigned char) c;
1083 	int r;
1084 
1085 	if (pendc && !before_pendc)
1086 	{
1087 		if (ch == '\r' && pendc == '\r')
1088 			return (0);
1089 		if (do_append(pendc, NULL, pendpos))
1090 			/*
1091 			 * Oops.  We've probably lost the char which
1092 			 * was in pendc, since caller won't back up.
1093 			 */
1094 			return (1);
1095 		pendc = '\0';
1096 	}
1097 
1098 	if (ch == '\r' && (proc_return == OPT_ON || (bs_mode == BS_SPECIAL && proc_return == OPT_OFF)))
1099 	{
1100 		if (mbc_buf_len > 0)  /* utf_mode must be on. */
1101 		{
1102 			/* Flush incomplete (truncated) sequence. */
1103 			r = flush_mbc_buf(mbc_pos);
1104 			mbc_buf_index = r + 1;
1105 			mbc_buf_len = 0;
1106 			if (r)
1107 				return (mbc_buf_index);
1108 		}
1109 
1110 		/*
1111 		 * Don't put the CR into the buffer until we see
1112 		 * the next char.  If the next char is a newline,
1113 		 * discard the CR.
1114 		 */
1115 		pendc = ch;
1116 		pendpos = pos;
1117 		return (0);
1118 	}
1119 
1120 	if (!utf_mode)
1121 	{
1122 		r = do_append(ch, NULL, pos);
1123 	} else
1124 	{
1125 		/* Perform strict validation in all possible cases. */
1126 		if (mbc_buf_len == 0)
1127 		{
1128 		retry:
1129 			mbc_buf_index = 1;
1130 			*mbc_buf = c;
1131 			if (IS_ASCII_OCTET(c))
1132 				r = do_append(ch, NULL, pos);
1133 			else if (IS_UTF8_LEAD(c))
1134 			{
1135 				mbc_buf_len = utf_len(c);
1136 				mbc_pos = pos;
1137 				return (0);
1138 			} else
1139 				/* UTF8_INVALID or stray UTF8_TRAIL */
1140 				r = flush_mbc_buf(pos);
1141 		} else if (IS_UTF8_TRAIL(c))
1142 		{
1143 			mbc_buf[mbc_buf_index++] = c;
1144 			if (mbc_buf_index < mbc_buf_len)
1145 				return (0);
1146 			if (is_utf8_well_formed(mbc_buf, mbc_buf_index))
1147 				r = do_append(get_wchar(mbc_buf), mbc_buf, mbc_pos);
1148 			else
1149 				/* Complete, but not shortest form, sequence. */
1150 				mbc_buf_index = r = flush_mbc_buf(mbc_pos);
1151 			mbc_buf_len = 0;
1152 		} else
1153 		{
1154 			/* Flush incomplete (truncated) sequence.  */
1155 			r = flush_mbc_buf(mbc_pos);
1156 			mbc_buf_index = r + 1;
1157 			mbc_buf_len = 0;
1158 			/* Handle new char.  */
1159 			if (!r)
1160 				goto retry;
1161 		}
1162 	}
1163 	if (r)
1164 	{
1165 		/* How many chars should caller back up? */
1166 		r = (!utf_mode) ? 1 : mbc_buf_index;
1167 	}
1168 	return (r);
1169 }
1170 
pappend(char c,POSITION pos)1171 public int pappend(char c, POSITION pos)
1172 {
1173 	if (ff_starts_line < 0)
1174 		ff_starts_line = (c == CONTROL('L'));
1175 	return pappend_b(c, pos, FALSE);
1176 }
1177 
line_is_ff(void)1178 public lbool line_is_ff(void)
1179 {
1180 	return (ff_starts_line == 1);
1181 }
1182 
store_control_char(LWCHAR ch,constant char * rep,POSITION pos)1183 static int store_control_char(LWCHAR ch, constant char *rep, POSITION pos)
1184 {
1185 	if (ctldisp == OPT_ON)
1186 	{
1187 		/* Output the character itself. */
1188 		STORE_CHAR(ch, AT_NORMAL, rep, pos);
1189 	} else
1190 	{
1191 		/* Output a printable representation of the character. */
1192 		STORE_PRCHAR(ch, pos);
1193 	}
1194 	return (0);
1195 }
1196 
1197 /*
1198  * Remove invalid ANSI sequence.
1199  */
remove_ansi(void)1200 static void remove_ansi(void)
1201 {
1202 	constant char *start = (cshift < hshift) ? xbuf_char_data(&shifted_ansi): linebuf.buf;
1203 	size_t *end = (cshift < hshift) ? &shifted_ansi.end : &linebuf.end;
1204 	constant char *p = start + *end;
1205 	LWCHAR bch;
1206 	do {
1207 		bch = step_charc(&p, -1, start);
1208 	} while (p > start && (!IS_CSI_START(bch) || line_ansi->escs_in_seq-- > 0));
1209 	*end = ptr_diff(p, start);
1210 	xbuf_reset(&last_ansi);
1211 }
1212 
store_ansi(LWCHAR ch,constant char * rep,POSITION pos)1213 static int store_ansi(LWCHAR ch, constant char *rep, POSITION pos)
1214 {
1215 	switch (ansi_step2(line_ansi, ch, pos != NULL_POSITION))
1216 	{
1217 	case ANSI_MID:
1218 		STORE_CHAR(ch, AT_ANSI, rep, pos);
1219 		switch (ansi_osc8_state(line_ansi))
1220 		{
1221 		case OSC_TYPENUM: case OSC_STRING: hlink_in_line = TRUE; break;
1222 		default: break;
1223 		}
1224 		xbuf_add_char(&last_ansi, (char) ch);
1225 		break;
1226 	case ANSI_END:
1227 		STORE_CHAR(ch, AT_ANSI, rep, pos);
1228 		ansi_done(line_ansi);
1229 		line_ansi = NULL;
1230 		xbuf_add_char(&last_ansi, (char) ch);
1231 		xbuf_set(&last_ansis[curr_last_ansi], &last_ansi);
1232 		xbuf_reset(&last_ansi);
1233 		curr_last_ansi = (curr_last_ansi + 1) % NUM_LAST_ANSIS;
1234 		break;
1235 	case ANSI_ERR:
1236 		remove_ansi();
1237 		ansi_done(line_ansi);
1238 		line_ansi = NULL;
1239 		break;
1240 	default:
1241 		break;
1242 	}
1243 	return (0);
1244 }
1245 
store_bs(LWCHAR ch,constant char * rep,POSITION pos)1246 static int store_bs(LWCHAR ch, constant char *rep, POSITION pos)
1247 {
1248 	if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1249 		return store_control_char(ch, rep, pos);
1250 	if (linebuf.end > 0 &&
1251 		((linebuf.end <= linebuf.print && linebuf.buf[linebuf.end-1] == '\0') ||
1252 	     (linebuf.end > 0 && linebuf.attr[linebuf.end - 1] & (AT_ANSI|AT_BINARY))))
1253 		STORE_PRCHAR('\b', pos);
1254 	else if (proc_backspace == OPT_OFF && bs_mode == BS_NORMAL)
1255 		STORE_CHAR(ch, AT_NORMAL, NULL, pos);
1256 	else if (proc_backspace == OPT_ON || (bs_mode == BS_SPECIAL && proc_backspace == OPT_OFF))
1257 		overstrike = backc();
1258 	return 0;
1259 }
1260 
do_append(LWCHAR ch,constant char * rep,POSITION pos)1261 static int do_append(LWCHAR ch, constant char *rep, POSITION pos)
1262 {
1263 	int a = AT_NORMAL;
1264 	int in_overstrike = overstrike;
1265 
1266 	if ((ctldisp == OPT_ONPLUS || pos == NULL_POSITION) && line_ansi == NULL)
1267 	{
1268 		line_ansi = ansi_start(ch);
1269 		if (line_ansi != NULL)
1270 			ansi_in_line = TRUE;
1271 	}
1272 
1273 	overstrike = 0;
1274 	if (line_ansi != NULL)
1275 		return store_ansi(ch, rep, pos);
1276 
1277 	if (ch == '\b')
1278 		return store_bs(ch, rep, pos);
1279 
1280 	if (in_overstrike > 0)
1281 	{
1282 		/*
1283 		 * Overstrike the character at the current position
1284 		 * in the line buffer.  This will cause either
1285 		 * underline (if a "_" is overstruck),
1286 		 * bold (if an identical character is overstruck),
1287 		 * or just replacing the character in the buffer.
1288 		 */
1289 		LWCHAR prev_ch;
1290 		overstrike = utf_mode ? -1 : 0;
1291 		if (utf_mode)
1292 		{
1293 			/* To be correct, this must be a base character.  */
1294 			prev_ch = get_wchar(&linebuf.buf[linebuf.end]);
1295 		} else
1296 		{
1297 			prev_ch = (unsigned char) linebuf.buf[linebuf.end];
1298 		}
1299 		a = linebuf.attr[linebuf.end];
1300 		if (ch == prev_ch)
1301 		{
1302 			/*
1303 			 * Overstriking a char with itself means make it bold.
1304 			 * But overstriking an underscore with itself is
1305 			 * ambiguous.  It could mean make it bold, or
1306 			 * it could mean make it underlined.
1307 			 * Use the previous overstrike to resolve it.
1308 			 */
1309 			if (ch == '_')
1310 			{
1311 				if ((a & (AT_BOLD|AT_UNDERLINE)) != AT_NORMAL)
1312 					a |= (AT_BOLD|AT_UNDERLINE);
1313 				else if (last_overstrike != AT_NORMAL)
1314 					a |= last_overstrike;
1315 				else
1316 					a |= AT_BOLD;
1317 			} else
1318 				a |= AT_BOLD;
1319 		} else if (ch == '_')
1320 		{
1321 			a |= AT_UNDERLINE;
1322 			ch = prev_ch;
1323 			rep = &linebuf.buf[linebuf.end];
1324 		} else if (prev_ch == '_')
1325 		{
1326 			a |= AT_UNDERLINE;
1327 		}
1328 		/* Else we replace prev_ch, but we keep its attributes.  */
1329 	} else if (in_overstrike < 0)
1330 	{
1331 		if (   is_composing_char(ch)
1332 		    || is_combining_char(get_wchar(&linebuf.buf[linebuf.end]), ch))
1333 			/* Continuation of the same overstrike.  */
1334 			a = last_overstrike;
1335 		else
1336 			overstrike = 0;
1337 	}
1338 
1339 	if (is_omit_char(ch))
1340 	{
1341 		if (bs_mode == BS_CONTROL)
1342 		{
1343 			if (utf_mode)
1344 				STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1345 			else
1346 				STORE_PRCHAR(ch, pos);
1347 		}
1348 		return (0); /* omit the character. */
1349 	}
1350 	if (ch == '\t')
1351 	{
1352 		/*
1353 		 * Expand a tab into spaces.
1354 		 */
1355 		if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1356 			return store_control_char(ch, rep, pos);
1357 		STORE_TAB(a, pos);
1358 		return (0);
1359 	}
1360 	if ((!utf_mode || is_ascii_char(ch)) && control_char(ch))
1361 	{
1362 		return store_control_char(ch, rep, pos);
1363 	} else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(ch))
1364 	{
1365 		STORE_STRING(prutfchar(ch), AT_BINARY, pos);
1366 	} else
1367 	{
1368 		STORE_CHAR(ch, a, rep, pos);
1369 	}
1370 	return (0);
1371 }
1372 
1373 /*
1374  *
1375  */
pflushmbc(void)1376 public int pflushmbc(void)
1377 {
1378 	int r = 0;
1379 
1380 	if (mbc_buf_len > 0)
1381 	{
1382 		/* Flush incomplete (truncated) sequence.  */
1383 		r = flush_mbc_buf(mbc_pos);
1384 		mbc_buf_len = 0;
1385 	}
1386 	return r;
1387 }
1388 
1389 /*
1390  * Switch to normal attribute at end of line.
1391  */
add_attr_normal(void)1392 static void add_attr_normal(void)
1393 {
1394 	if (line_ansi != NULL)
1395 	{
1396 		switch (line_ansi->ostate)
1397 		{
1398 		case OSC_START:
1399 		case OSC_END:
1400 			break;
1401 		default:
1402 			/* We're in an unterminated OSC sequence; remove it. */
1403 			remove_ansi();
1404 			break;
1405 		}
1406 		ansi_done(line_ansi);
1407 		line_ansi = NULL;
1408 	}
1409 	if (ctldisp != OPT_ONPLUS || !is_ansi_end('m'))
1410 		return;
1411 	addstr_linebuf("\033[m", AT_ANSI, 0);
1412 	if (hlink_in_line) /* Don't send hyperlink clear if we know we don't need to. */
1413 		addstr_linebuf("\033]8;;\033\\", AT_ANSI, 0);
1414 }
1415 
1416 /*
1417  * Terminate the line in the line buffer.
1418  */
pdone(lbool endline,lbool chopped,lbool forw,lbool full_pad)1419 public void pdone(lbool endline, lbool chopped, lbool forw, lbool full_pad)
1420 {
1421 	(void) pflushmbc();
1422 	linebuf.prev_end = (!endline && !chopped) ? linebuf.end : 0;
1423 
1424 	if (pendc && (pendc != '\r' || !endline))
1425 		/*
1426 		 * If we had a pending character, put it in the buffer.
1427 		 * But discard a pending CR if we are at end of line
1428 		 * (that is, discard the CR in a CR/LF sequence).
1429 		 */
1430 		(void) do_append(pendc, NULL, pendpos);
1431 
1432 	if (chopped && rscroll_char)
1433 	{
1434 		char rscroll_utf8[MAX_UTF_CHAR_LEN+1];
1435 		char *up = rscroll_utf8;
1436 
1437 		/*
1438 		 * Display the right scrolling char.
1439 		 * If we've already filled the rightmost screen char
1440 		 * (in the buffer), overwrite it.
1441 		 */
1442 		if (end_column >= sc_width + cshift)
1443 		{
1444 			/* We've already written in the rightmost char. */
1445 			end_column = right_column;
1446 			linebuf.end = (size_t) right_curr;
1447 		}
1448 		add_attr_normal();
1449 		while (end_column < sc_width-1 + cshift)
1450 		{
1451 			/*
1452 			 * Space to last (rightmost) char on screen.
1453 			 * This may be necessary if the char we overwrote
1454 			 * was double-width.
1455 			 */
1456 			add_linebuf(' ', 0, 1);
1457 		}
1458 		/* Print rscroll char. */
1459 		put_wchar(&up, rscroll_char);
1460 		*up = '\0';
1461 		addstr_linebuf(rscroll_utf8, rscroll_attr, 0);
1462 		inc_end_column(1); /* assume rscroll_char is single-width */
1463 	} else
1464 	{
1465 		add_attr_normal();
1466 	}
1467 
1468 	/*
1469 	 * If we're coloring a status line, fill out the line with spaces.
1470 	 */
1471 	if (status_line && (line_mark_attr != 0 || full_pad)) {
1472 		while (end_column < sc_width + cshift)
1473 			add_linebuf(' ', line_mark_attr, 1);
1474 	}
1475 
1476 	/*
1477 	 * Add a newline if necessary,
1478 	 * and append a '\0' to the end of the line.
1479 	 * We output a newline if we're not at the right edge of the screen,
1480 	 * or if the terminal doesn't auto wrap,
1481 	 * or if this is really the end of the line AND the terminal ignores
1482 	 * a newline at the right edge.
1483 	 * (In the last case we don't want to output a newline if the terminal
1484 	 * doesn't ignore it since that would produce an extra blank line.
1485 	 * But we do want to output a newline if the terminal ignores it in case
1486 	 * the next line is blank.  In that case the single newline output for
1487 	 * that blank line would be ignored!)
1488 	 */
1489 	if (end_column < sc_width + cshift || !auto_wrap || (endline && defer_wrap) || ctldisp == OPT_ON)
1490 	{
1491 		add_linebuf('\n', AT_NORMAL, 0);
1492 	}
1493 	else if (defer_wrap && end_column >= sc_width + cshift && forw)
1494 	{
1495 		/*
1496 		 * Terminals with "defer_wrap" don't wrap until they *really* need
1497 		 * to, i.e. when the character *after* the last one to fit on a
1498 		 * line is output. But they are too hard to deal with when they
1499 		 * get in the state where a full screen width of characters
1500 		 * have been output but the cursor is sitting on the right edge
1501 		 * instead of at the start of the next line.
1502 		 * So we nudge them into wrapping by outputting a space
1503 		 * character plus a backspace.  But do this only if moving
1504 		 * forward; if we're moving backward and drawing this line at
1505 		 * the top of the screen, the space would overwrite the first
1506 		 * char on the next line.  We don't need to do this "nudge"
1507 		 * at the top of the screen anyway.
1508 		 */
1509 		add_linebuf(' ', AT_NORMAL, 1);
1510 		add_linebuf('\b', AT_NORMAL, -1);
1511 	}
1512 	/*
1513 	 * If a terminal moves the cursor to the next line immediately after
1514 	 * writing into the last char of a line, the following line may get
1515 	 * colored with the last char's background color before the color
1516 	 * reset sequence is sent. Clear the line to reset the background color.
1517 	 */
1518 	if (auto_wrap && !defer_wrap && end_column >= sc_width + cshift)
1519 		clear_after_line = TRUE;
1520 	set_linebuf(linebuf.end, '\0', AT_NORMAL);
1521 }
1522 
1523 /*
1524  * Return the column number (screen position) of a given file position in its line.
1525  * linepos = position of first char in line
1526  * spos = position of char being queried
1527  * saved_pos = position of a known column, or NULL_POSITION if no known column
1528  * saved_col = column number of a known column, or -1 if no known column
1529  *
1530  * This attempts to mimic the logic in pappend() and the store_*() functions.
1531  * Duplicating this complicated logic is not a good design.
1532  */
1533 
1534 struct col_pos { int col; POSITION pos; };
1535 
col_vs_pos(POSITION linepos,mutable struct col_pos * cp,POSITION saved_pos,int saved_col)1536 static void col_vs_pos(POSITION linepos, mutable struct col_pos *cp, POSITION saved_pos, int saved_col)
1537 {
1538 	int col = (saved_col < 0) ? 0 : saved_col;
1539 	LWCHAR prev_ch = 0;
1540 	struct ansi_state *pansi = NULL;
1541 	char utf8_buf[MAX_UTF_CHAR_LEN];
1542 	int utf8_len = 0;
1543 	POSITION chpos;
1544 
1545 	if (ch_seek(saved_pos != NULL_POSITION ? saved_pos : linepos))
1546 		return;
1547 	for (;;)
1548 	{
1549 		int ich;
1550 		char ch;
1551 		int cw = 0;
1552 
1553 		chpos = ch_tell();
1554 		ich = ch_forw_get();
1555 		ch = (char) ich;
1556 		if (ich == EOI || ch == '\n')
1557 			break;
1558 		if (pansi != NULL)
1559 		{
1560 			if (ansi_step(pansi, ch) != ANSI_MID)
1561 			{
1562 				ansi_done(pansi);
1563 				pansi = NULL;
1564 			}
1565 		} else if (ctldisp == OPT_ONPLUS && (pansi = ansi_start(ch)) != NULL)
1566 		{
1567 			/* start of ansi sequence */
1568 			(void) ansi_step(pansi, ch);
1569 		} else if (ch == '\b')
1570 		{
1571 			if (proc_backspace == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_backspace == OPT_OFF))
1572 				cw = (int) strlen(prchar(ch));
1573 			else
1574 				cw = (utf_mode && is_wide_char(prev_ch)) ? -2 : -1;
1575 		} else if (ch == '\t')
1576 		{
1577 			if (proc_tab == OPT_ONPLUS || (bs_mode == BS_CONTROL && proc_tab == OPT_OFF))
1578 				cw = (int) strlen(prchar(ch));
1579 			else
1580 				cw = tab_spaces(col);
1581 		} else if ((!utf_mode || is_ascii_char(ch)) && control_char(ch))
1582 		{
1583 			cw = (int) strlen(prchar(ch));
1584 		} else if (utf8_len < MAX_UTF_CHAR_LEN)
1585 		{
1586 			utf8_buf[utf8_len++] = ch;
1587 			if (is_utf8_well_formed(utf8_buf, utf8_len))
1588 			{
1589 				LWCHAR wch = get_wchar(utf8_buf);
1590 				int attr = 0; /* {{ ignoring attribute is not correct for magic cookie terminals }} */
1591 				utf8_len = 0;
1592 				if (is_omit_char(wch))
1593 				{
1594 					if (bs_mode == BS_CONTROL)
1595 						cw = strlen(utf_mode ? prutfchar(wch) : prchar(wch));
1596 				} else if (utf_mode && ctldisp != OPT_ON && is_ubin_char(wch))
1597 					cw = (int) strlen(prutfchar(wch));
1598 				else
1599 					cw = pwidth(wch, attr, prev_ch, attr);
1600 				prev_ch = wch;
1601 			}
1602 		} else
1603 		{
1604 			utf8_len = 0; /* flush invalid UTF-8 */
1605 		}
1606 
1607 		if (cp->pos != NULL_POSITION && chpos == cp->pos) /* found the position we want */
1608 			break;
1609 		if (cp->col >= 0 && col >= cp->col && cw > 0) /* found the column we want */
1610 			break;
1611 		col += cw;
1612 		prev_ch = ch;
1613 	}
1614 	cp->col = col;
1615 	cp->pos = chpos;
1616 }
1617 
col_from_pos(POSITION linepos,POSITION spos,POSITION saved_pos,int saved_col)1618 public int col_from_pos(POSITION linepos, POSITION spos, POSITION saved_pos, int saved_col)
1619 {
1620 	struct col_pos cp;
1621 	cp.pos = spos;
1622 	cp.col = -1;
1623 	col_vs_pos(linepos, &cp, saved_pos, saved_col);
1624 	return cp.col;
1625 }
1626 
pos_from_col(POSITION linepos,int col,POSITION saved_pos,int saved_col)1627 public POSITION pos_from_col(POSITION linepos, int col, POSITION saved_pos, int saved_col)
1628 {
1629 	struct col_pos cp;
1630 	cp.col = col + hshift - line_pfx_width();
1631 	cp.pos = NULL_POSITION;
1632 	col_vs_pos(linepos, &cp, saved_pos, saved_col);
1633 	return cp.pos;
1634 }
1635 
1636 /*
1637  * Set an attribute on each char of the line in the line buffer.
1638  */
set_attr_line(int a)1639 public void set_attr_line(int a)
1640 {
1641 	size_t i;
1642 
1643 	for (i = linebuf.print;  i < linebuf.end;  i++)
1644 		if ((linebuf.attr[i] & AT_COLOR) == 0 || (a & AT_COLOR) == 0)
1645 			linebuf.attr[i] |= a;
1646 }
1647 
1648 /*
1649  * Set the char to be displayed in the status column.
1650  */
set_status_col(char c,int attr)1651 public void set_status_col(char c, int attr)
1652 {
1653 	set_pfx(0, c, attr);
1654 }
1655 
1656 /*
1657  * Get a character from the current line.
1658  * Return the character as the function return value,
1659  * and the character attribute in *ap.
1660  */
gline(size_t i,int * ap)1661 public int gline(size_t i, int *ap)
1662 {
1663 	if (is_null_line)
1664 	{
1665 		/*
1666 		 * If there is no current line, we pretend the line is
1667 		 * either "~" or "", depending on the "twiddle" flag.
1668 		 */
1669 		if (twiddle)
1670 		{
1671 			if (i == 0)
1672 			{
1673 				*ap = use_color ? AT_COLOR_TILDE : AT_BOLD;
1674 				return '~';
1675 			}
1676 			--i;
1677 		}
1678 		/* Make sure we're back to AT_NORMAL before the '\n'.  */
1679 		*ap = AT_NORMAL;
1680 		return i ? '\0' : '\n';
1681 	}
1682 
1683 	if (i < linebuf.pfx_end)
1684 	{
1685 		*ap = linebuf.pfx_attr[i];
1686 		return linebuf.pfx[i];
1687 	}
1688 	i += linebuf.print - linebuf.pfx_end;
1689 	*ap = linebuf.attr[i];
1690 	return (unsigned char) linebuf.buf[i];
1691 }
1692 
1693 /*
1694  * Should we clear to end of line after printing this line?
1695  */
should_clear_after_line(void)1696 public lbool should_clear_after_line(void)
1697 {
1698 	return clear_after_line;
1699 }
1700 
1701 /*
1702  * Indicate that there is no current line.
1703  */
null_line(void)1704 public void null_line(void)
1705 {
1706 	is_null_line = TRUE;
1707 	cshift = 0;
1708 }
1709 
1710 /*
1711  * Analogous to forw_line(), but deals with "raw lines":
1712  * lines which are not split for screen width.
1713  * {{ This is supposed to be more efficient than forw_line(). }}
1714  */
forw_raw_line_len(POSITION curr_pos,size_t read_len,constant char ** linep,size_t * line_lenp)1715 public POSITION forw_raw_line_len(POSITION curr_pos, size_t read_len, constant char **linep, size_t *line_lenp)
1716 {
1717 	size_t n;
1718 	int c;
1719 	POSITION new_pos;
1720 
1721 	if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
1722 		(c = ch_forw_get()) == EOI)
1723 		return (NULL_POSITION);
1724 
1725 	set_line_contig_pos(NULL_POSITION);
1726 	n = 0;
1727 	for (;;)
1728 	{
1729 		if (c == '\n' || c == EOI || ABORT_SIGS())
1730 		{
1731 			new_pos = ch_tell();
1732 			break;
1733 		}
1734 		if (n >= size_linebuf-1)
1735 		{
1736 			if (expand_linebuf())
1737 			{
1738 				/*
1739 				 * Overflowed the input buffer.
1740 				 * Pretend the line ended here.
1741 				 */
1742 				new_pos = ch_tell() - 1;
1743 				break;
1744 			}
1745 		}
1746 		linebuf.buf[n++] = (char) c;
1747 		if (read_len != size_t_null && read_len > 0 && n >= read_len)
1748 		{
1749 			new_pos = ch_tell();
1750 			break;
1751 		}
1752 		c = ch_forw_get();
1753 	}
1754 	linebuf.buf[n] = '\0';
1755 	if (linep != NULL)
1756 		*linep = linebuf.buf;
1757 	if (line_lenp != NULL)
1758 		*line_lenp = n;
1759 	return (new_pos);
1760 }
1761 
forw_raw_line(POSITION curr_pos,constant char ** linep,size_t * line_lenp)1762 public POSITION forw_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp)
1763 {
1764 	return forw_raw_line_len(curr_pos, size_t_null, linep, line_lenp);
1765 }
1766 
1767 /*
1768  * Analogous to back_line(), but deals with "raw lines".
1769  * {{ This is supposed to be more efficient than back_line(). }}
1770  */
back_raw_line(POSITION curr_pos,constant char ** linep,size_t * line_lenp)1771 public POSITION back_raw_line(POSITION curr_pos, constant char **linep, size_t *line_lenp)
1772 {
1773 	size_t n;
1774 	int c;
1775 	POSITION new_pos;
1776 
1777 	if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() ||
1778 		ch_seek(curr_pos-1))
1779 		return (NULL_POSITION);
1780 
1781 	set_line_contig_pos(NULL_POSITION);
1782 	n = size_linebuf;
1783 	linebuf.buf[--n] = '\0';
1784 	for (;;)
1785 	{
1786 		c = ch_back_get();
1787 		if (c == '\n' || ABORT_SIGS())
1788 		{
1789 			/*
1790 			 * This is the newline ending the previous line.
1791 			 * We have hit the beginning of the line.
1792 			 */
1793 			new_pos = ch_tell() + 1;
1794 			break;
1795 		}
1796 		if (c == EOI)
1797 		{
1798 			/*
1799 			 * We have hit the beginning of the file.
1800 			 * This must be the first line in the file.
1801 			 * This must, of course, be the beginning of the line.
1802 			 */
1803 			new_pos = ch_zero();
1804 			break;
1805 		}
1806 		if (n <= 0)
1807 		{
1808 			size_t old_size_linebuf = size_linebuf;
1809 			char *fm;
1810 			char *to;
1811 			if (expand_linebuf())
1812 			{
1813 				/*
1814 				 * Overflowed the input buffer.
1815 				 * Pretend the line ended here.
1816 				 */
1817 				new_pos = ch_tell() + 1;
1818 				break;
1819 			}
1820 			/*
1821 			 * Shift the data to the end of the new linebuf.
1822 			 */
1823 			for (fm = linebuf.buf + old_size_linebuf - 1,
1824 			      to = linebuf.buf + size_linebuf - 1;
1825 			     fm >= linebuf.buf;  fm--, to--)
1826 				*to = *fm;
1827 			n = size_linebuf - old_size_linebuf;
1828 		}
1829 		linebuf.buf[--n] = (char) c;
1830 	}
1831 	if (linep != NULL)
1832 		*linep = &linebuf.buf[n];
1833 	if (line_lenp != NULL)
1834 		*line_lenp = size_linebuf - 1 - n;
1835 	return (new_pos);
1836 }
1837 
1838 /*
1839  * Skip cols printable columns at the start of line.
1840  * Return number of bytes skipped.
1841  */
skip_columns(int cols,constant char ** linep,size_t * line_lenp)1842 public int skip_columns(int cols, constant char **linep, size_t *line_lenp)
1843 {
1844 	constant char *line = *linep;
1845 	constant char *eline = line + *line_lenp;
1846 	LWCHAR pch = 0;
1847 	size_t bytes;
1848 
1849 	while (cols > 0 && line < eline)
1850 	{
1851 		LWCHAR ch = step_charc(&line, +1, eline);
1852 		struct ansi_state *pansi = ansi_start(ch);
1853 		if (pansi != NULL)
1854 		{
1855 			skip_ansi(pansi, ch, &line, eline);
1856 			ansi_done(pansi);
1857 			pch = 0;
1858 		} else
1859 		{
1860 			int w = pwidth(ch, 0, pch, 0);
1861 			cols -= w;
1862 			pch = ch;
1863 		}
1864 	}
1865 	bytes = ptr_diff(line, *linep);
1866 	*linep = line;
1867 	*line_lenp -= bytes;
1868 	return (int) bytes; /*{{type-issue}}*/
1869 }
1870 
1871 /*
1872  * Append a string to the line buffer.
1873  */
pappstr(constant char * str)1874 static int pappstr(constant char *str)
1875 {
1876 	while (*str != '\0')
1877 	{
1878 		if (pappend(*str++, NULL_POSITION))
1879 			/* Doesn't fit on screen. */
1880 			return 1;
1881 	}
1882 	return 0;
1883 }
1884 
1885 /*
1886  * Load a string into the line buffer.
1887  * If the string is too long to fit on the screen (minus reserve chars),
1888  * truncate the beginning of the string to fit.
1889  */
load_line(constant char * str,int attr,int reserve)1890 public void load_line(constant char *str, int attr, int reserve)
1891 {
1892 	int save_hshift = hshift;
1893 	hshift = 0;
1894 
1895 	/* We're overwriting the line buffer, so what's in it will no longer be contiguous. */
1896 	set_line_contig_pos(NULL_POSITION);
1897 
1898 	sc_width -= reserve;
1899 	for (;;)
1900 	{
1901 		prewind(FALSE);
1902 		if (pappstr(str) == 0)
1903 			break;
1904 		/*
1905 		 * Didn't fit on screen; increase left shift by one.
1906 		 * {{ This gets very inefficient if the string
1907 		 * is much longer than the screen width. }}
1908 		 */
1909 		hshift += 1;
1910 	}
1911 	set_linebuf(linebuf.end, '\0', AT_NORMAL);
1912 	linebuf.prev_end = 0;
1913 	sc_width += reserve;
1914 
1915 	/* Color the prompt unless it has ansi sequences in it. */
1916 	if (!ansi_in_line)
1917 	{
1918 		size_t i;
1919 		for (i = linebuf.print;  i < linebuf.end;  i++)
1920 			set_linebuf(i, linebuf.buf[i], attr);
1921 	}
1922 	hshift = save_hshift;
1923 }
1924 
1925 /*
1926  * Find the length of the longest displayed line on the screen.
1927  */
longest_line_width(void)1928 public int longest_line_width(void)
1929 {
1930 	POSITION pos;
1931 	int save_width;
1932 	int sindex;
1933 	int longest = 0;
1934 
1935 	save_width = sc_width;
1936 	sc_width = INT_MAX; /* so forw_line() won't chop */
1937 	for (sindex = TOP; sindex < sc_height-1; sindex++)
1938 		if ((pos = position(sindex)) != NULL_POSITION)
1939 			break;
1940 	for (; sindex < sc_height-1 && pos != NULL_POSITION; sindex++)
1941 	{
1942 		pos = forw_line(pos, NULL, NULL);
1943 		if (end_column > longest)
1944 			longest = end_column;
1945 	}
1946 	sc_width = save_width;
1947 	return longest;
1948 }
1949 
1950 /*
1951  * Find the shift necessary to show the end of the longest displayed line.
1952  */
rrshift(void)1953 public int rrshift(void)
1954 {
1955 	int longest = longest_line_width();
1956 	if (longest < sc_width)
1957 		return 0;
1958 	return longest - sc_width;
1959 }
1960 
1961 /*
1962  * Get the color_map index associated with a given attribute.
1963  */
lookup_color_index(int attr)1964 static int lookup_color_index(int attr)
1965 {
1966 	int cx;
1967 	for (cx = 0;  cx < countof(color_map);  cx++)
1968 		if (color_map[cx].attr == attr)
1969 			return cx;
1970 	return -1;
1971 }
1972 
color_index(int attr)1973 static int color_index(int attr)
1974 {
1975 	if (use_color && (attr & AT_COLOR))
1976 		return lookup_color_index(attr & AT_COLOR);
1977 	if (attr & AT_UNDERLINE)
1978 		return lookup_color_index(AT_UNDERLINE);
1979 	if (attr & AT_BOLD)
1980 		return lookup_color_index(AT_BOLD);
1981 	if (attr & AT_BLINK)
1982 		return lookup_color_index(AT_BLINK);
1983 	if (attr & AT_STANDOUT)
1984 		return lookup_color_index(AT_STANDOUT);
1985 	return -1;
1986 }
1987 
1988 /*
1989  * Set the color string to use for a given attribute.
1990  */
set_color_map(int attr,constant char * colorstr)1991 public int set_color_map(int attr, constant char *colorstr)
1992 {
1993 	int cx = color_index(attr);
1994 	if (cx < 0)
1995 		return -1;
1996 	if (strlen(colorstr)+1 > sizeof(color_map[cx].color))
1997 		return -1;
1998 	if (*colorstr != '\0' && parse_color(colorstr, NULL, NULL, NULL) == CT_NULL)
1999 		return -1;
2000 	strcpy(color_map[cx].color, colorstr);
2001 	return 0;
2002 }
2003 
2004 /*
2005  * Get the color string to use for a given attribute.
2006  */
get_color_map(int attr)2007 public constant char * get_color_map(int attr)
2008 {
2009 	int cx = color_index(attr);
2010 	if (cx < 0)
2011 		return NULL;
2012 	return color_map[cx].color;
2013 }
2014